Fully reproducible NixOS builds
One of big strengths of NixOS is reproducible builds. Yet achieving
full reproducibility requires a bit of effort. The minimal set of
things needed is a configuration.nix and a reference to a
nixpkgs commit. And the easiest way to achieve this is to have a
git-repo with configuration, which also references nixpkgs as a
submodule.
Let’s change an existing configuration that way.
cd /etc/nixos
git init
git commit -m Initial --allow-empty
git add *.nix
git commit -m "Import config"
git submodule add https://github.com/NixOS/nixpkgs
# Or a branch like nixos-18.03 from
# https://github.com/nixos/nixpkgs-channels to use stable version,
# with most of the things available from binary cache.Now we need to tell nix that we want to use our own checkout of
nixpkgs instead of channels. This can be achieved by adding an
option to configuration.nix:
nix.nixPath = [
"nixpkgs=/etc/nixos/nixpkgs"
"nixos-config=/etc/nixos/configuration.nix"
];After issuing nixos-rebuild switch twice 1
your own checkout of nixpkgs will be finally used.
But what if you want to build exactly the same configuration on
some other machine, like when doing CI? The manpage (and
source-code) of nixos-rebuild suggests that we can do it that
way:
nix build -f '<nixpkgs/nixos>' system \
-I nixpkgs=$(pwd)/nixpkgs \
-I nixos-config=$(pwd)/configuration.nixAnd it will indeed work if we’ll build it exactly from the same
directory on disk (i.e. /etc/nixos). Sadly trying to build the same
thing from another directory will produce different path in /nix/store/.
After a bit of digging I found out that difference is in how full
nixpkgs version is determined (it includes a git commit, and is a
bit flaky because it tries to parse a commit in pure nix). This is
later used both as a part of derivation name, and inside some files
like /etc/os-release.
It’s possible to override those auto-detected values with a following configuration snippet:
{pkgs, lib, ...}:
let
gitRepo = "${toString pkgs.path}/../.git";
gitCommitId = lib.substring 0 7 (lib.commitIdFromGitRepo gitRepo);
in {
system.nixos.versionSuffix = "-my-name-${gitCommitId}";
system.nixos.label = "my-name-${gitCommitId}";
}It’s 2 options that need to be set, and it’s even more useful when
done as shown above: derivation name and /etc/os-release will
contain reference both to an exact version of configuration.nix and
of an nixpkgs commit (as they’re both taken into account for
configuration repository commit id calculation).
So now we can commit everything, do nixos-rebuild switch and take
a note of what exactly was built:
readlink -f /run/current-systemIf we checkout our configuration repo somewhere else (at least in
another directory, and maybe even on another server) and do nix build command from above, we should finally see exactly the same
store path:
nix build ... # as above
ls -la result-
first time just to change the path, second time to really use your own
nixpkgs↩︎