Your First Flake
Note: flakes are still an “experimental” (but recommended) feature of
nix. In order to use them you will need to enable them. Write this line
to ~/.config/nix/nix.conf
:
experimental-features = nix-command flakes
In this example, we’ll instead use something called a flake
.
If you are used to using another language’s package manager (like
npm
or uv
or cargo
) then you are probably familiar with the
pattern of having a dependency file with an associated lockfile.
In nix, flake.nix
is our dependency file, and evaluating it
will produce a flake.lock
file for us.
{
description = "A minimal development environment with bash.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, nixpkgs }:
let
system = "aarch64-darwin";
pkgs = import nixpkgs { inherit system; };
in
{
devShell.${system} = pkgs.mkShell {
name = "bash-dev";
buildInputs = [ pkgs.bash ];
shellHook = ''
echo "Welcome to the bash development environment!"
echo "Type 'exit' to leave."
'';
};
};
}
Running nix develop
will both evaluate our flake.nix
file and
also start up a new shell for us using our declared dependencies.
$ nix develop
Welcome to the bash development environment!
Type 'exit' to leave.
$ echo $SHELL
/nix/store/x7m765hh1m4yikg9spw18lxldddvdnd0-bash-5.2p37/bin/bash
Evaluating our new flake.lock
file will show us all of our new
immutable dependencies.
Just as with other package managers, we could now commit this
flake.lock
file and be 100% certain that other people using this
repository will use exactly the same shell that we are!
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1740019556,
"narHash": "sha256-vn285HxnnlHLWnv59Og7muqECNMS33mWLM14soFIv2g=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dad564433178067be1fbdfcce23b546254b6d641",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
At this point you should feel free to move on to the next example, but if you would like to do a slightly deeper dive into what is happening here, read on!
Let’s start by thinking about our non-nix bash
. If you’re running homebrew on a mac, then your output probably looks similar.
$ which bash
/opt/homebrew/bin/bash
$ otool -L $(which bash)
/opt/homebrew/bin/bash:
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 2420.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)
What’s that otool
call? Well, let’s find out!
An important piece of reproducible builds is dynamically-linked code.
The list that otool
gave us is all of the libraries that bash
dynamically links to. Part of what this means is that if I were to copy
and paste this exact bash
binary from my machine to another machine on
which those libraries are different (or missing) then my bash
might not
run or might behave differently.
When nix
talks about hermetic builds and pure vs. impure builds
this is exactly the kind of impurity that it’s talking about!
$ man otool
The otool-classic command displays specified parts of object files or
libraries. It is the preferred tool for inspecting Mach-O binaries,
especially for binaries that are bad, corrupted, or fuzzed. It is also
useful in situations when inspecting files with new or "bleeding-edge"
Mach-O file format changes.
-L Display the names and version numbers of the shared libraries that
the object file uses, as well as the shared library ID if the file
is a shared library.
What about our nix
built bash
?
A couple of differences to note:
- The first two lines from our homebrew bash (
libncurses
andlibiconv
) are gone - The last two lines (
CoreFoundation
andlibSystem.B
) are still there. - It might seem surprising that even the
nix
-builtbash
links dynamically against MacOS core libraries, but this is normal. Apple has very strict backwards compatibility, so it’s generally safe to link against those dependencies. On Linux we would see different behavior (nix
would explicitly link against anix
-provided version oflibc
).
$ nix-shell
Welcome to the bash development environment!
Type 'exit' to leave.
$ otool -L $(echo $SHELL)
/nix/store/x7m765hh1m4yikg9spw18lxldddvdnd0-bash-5.2p37/bin/bash:
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1775.118.101)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.100.5)