Compile NixOS derivations into a single file for a single host

Alright, so after a while, one of the Nix maintainers commented on the issue:

You can nix copy a derivation and its closure.

I was under the impression that NixOS configurations were built like profiles, without a deriver, but that is not the case.

I think this confusion came from this post of yours:

This is incorrect! --query --deriver always outputs a derivation, not the output. This is why it’s important to never copy-paste the output of commands without the command you actually ran.

Full instructions

I’ve set up my own NixOS server in the meantime, so now I went through all the steps myself, and this is what worked for me:

Preparation

Make sure that nix --version returns at least 2.15.1. Older version might work, but we know for sure that 2.13.3 and 2.13.5 do not!

Building

First, build the system configuration:

$ nixos-rebuild build --flake path/to/flake
building the system configuration...
$ readlink result
/nix/store/r8y8a8gckycx95xdw8wnw1vq5dc13bll-nixos-system-foobar-23.11.20230812.f045184
$ nix-store -q --deriver result
/nix/store/gccqy8z0f78ip7kc4h8s87bb5khg08w0-nixos-system-foobar-23.11.20230812.f045184.drv

Remember the result of readlink result, that’s what we’re trying to replicate!

Copying to a local binary cache

Note the resulting path of nix-store -q --deriver is a derivation, and we can nix copy that quite easily:

$ configDrv=/nix/store/gccqy8z0f78ip7kc4h8s87bb5khg08w0-nixos-system-foobar-23.11.20230812.f045184.drv
$ nix copy $configDrv --to file:///tmp/configCache

Now, don’t be scared, it will look like this command copies like over 10GB of files, but actually, the binary cache we wrote to is pretty small, especially after compressing:

$ cd /tmp
$ du -sh configCache
20M	configCache
$ zip -r config.zip configCache
  adding: configCache/ (stored 0%)
  [ ... ]
$ du -sh config.zip
5.6M	config.zip
$ tar -czvf config.tar.gz configCache
configCache/
[...]
$ du -sh config.tar.gz
4.8M	config.tar.gz

To confirm the derivation has been copied properly, you can run this command:

$ nix store ls --store file:///tmp/configCache $configDrv
gccqy8z0f78ip7kc4h8s87bb5khg08w0-nixos-system-foobar-23.11.20230812.f045184.drv

Moving and recreating the config on another machine

Copy the file to my other machine (to avoid confusion, I copied it to the same path /tmp/config.tar.gz), uncompress it, and nix copy it to the local store:

$ cd /tmp
$ tar -xvzf config.tar.gz
x configCache/
[...]
$ configDrv=/nix/store/gccqy8z0f78ip7kc4h8s87bb5khg08w0-nixos-system-foobar-23.11.20230812.f045184.drv
$ nix copy $configDrv --from file:///tmp/configCache

Now, you can build the configuration like so, and check the result.

$ nix build "${configDrv}^*"
$ readlink result
/nix/store/r8y8a8gckycx95xdw8wnw1vq5dc13bll-nixos-system-foobar-23.11.20230812.f045184

This should be the exact same as when we ran readlink result after nixos-rebuild on the original machine.

Activating the config

Finally, activating the configuration is as easy as:

$ ./result/activate

Let me know if this works for you!

Potential caveats

BTW, I found that running the nix copy command like this:

$ nix copy $(nix-store -q --deriver result) --to file:///tmp/configCache

Did not work. I’m not entirely sure why, maybe I did something wrong, but there doesn’t seem to be an obvious problem with this.

6 Likes