Nix 101: Part 3
The third part of Nix 101 will explain and go through nix flakes.
Flakes - what are they and why
Nix Flakes are another way to create reproducible environments. They are arguably more reproducible than shells and are the community favoured strategy in creating environments.
Flakes improve on Nix Shells by addressing the problem inherent in pulling dependencies from Nixpkgs.
In Nix shells, the upstream version of Nixpkgs is not locked, meaning two engineers using the same shell file might use different versions of Nixpkgs (e.g. nixpkgs 1.2.3 vs nixpkgs 1.2.4). This could result in different versions of the dependencies being pulled, leading to breaking builds or inconsistencies between the different environments.
With Nix Flakes, the version of nixpkgs and source is explicitly declared, this generates a flake.lock
file that locks in the version.
Example Flake
Using our zig shell in the previous blog post, we’ll create a flake to lock in the nixpkg dependency version.
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
# Helper function to build different versions of each zig version.
zigDerivation = { version, hash } :
pkgs.stdenv.mkDerivation {
pname = "zig";
version = version;
src = pkgs.fetchurl {
url = "https://ziglang.org/download/${version}/zig-linux-x86_64-${version}.tar.xz";
sha256 = hash;
};
buildPhase = ''
mkdir -p $out/bin
cp ./zig $out/bin
'';
};
in
{
packages = {
x86_64-linux.zig0130 = zigDerivation { version = "0.13.0"; hash = "1FMS5h68xIAyt3vEz3/WkVwR+hbkqtEWtmyUaCESMOo="; };
x86_64-linux.zig0120 = zigDerivation { version = "0.12.0"; hash = "x66Ga4p2pWji1c/TH+ic22Kb3RYf3VAYsppKChcEXK0="; };
};
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0130 ];
};
devShells.x86_64-linux.zig0130 = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0130 ];
};
devShells.x86_64-linux.zig0120 = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0120 ];
};
};
}
There’s a lot more going on than the shell, we’ll step through each line:
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
- This line declares the upstream source which will contain the version of nixpkgs for this flakeWe create the derivation and build phase for the flake. We are downloading and going to use the parameters of
version
andhash
when creating this derivationThis will download the binary using fetchurl and copy the binary to the
$out/bin
directory, which is the environment for the shellImportant to note, this is still not totally reproducible since this flake would only work correctly on systems that are
x86_64-linux
outputs = { self, nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; };
# Helper function to build different versions of each zig version.
zigDerivation = { version, hash } :
pkgs.stdenv.mkDerivation {
pname = "zig";
version = version;
src = pkgs.fetchurl {
url = "https://ziglang.org/download/${version}/zig-linux-x86_64-${version}.tar.xz";
sha256 = hash;
};
buildPhase = ''
mkdir -p $out/bin
cp ./zig $out/bin
'';
};
in
{
- After definig the inputs (the derivation, system and pkgs) we create the zig derivation for certain versions (0.13.0, 0.12.0)
- We create the shells for each version, using the packages built from the derivations
in
{
packages = {
x86_64-linux.zig0130 = zigDerivation { version = "0.13.0"; hash = "1FMS5h68xIAyt3vEz3/WkVwR+hbkqtEWtmyUaCESMOo="; };
x86_64-linux.zig0120 = zigDerivation { version = "0.12.0"; hash = "x66Ga4p2pWji1c/TH+ic22Kb3RYf3VAYsppKChcEXK0="; };
};
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0130 ];
};
devShells.x86_64-linux.zig0130 = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0130 ];
};
devShells.x86_64-linux.zig0120 = pkgs.mkShell {
buildInputs = [ self.packages.${system}.zig0120 ];
};
};
}
And finally, to run the dev shell within the flake:
// example: nix develop <path-to-flake-folder>/#<shell-name>
nix develop ./zig-flake/#zig0130
A flake.lock
file will be generated after running the flake:
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1722185531,
"narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
Summary
Flakes solve a reproducibility issue in nix shells by pinning the upstream nixpkg version, allowing different engineers to run the same flake and ensure they have the exact same dependencies.