Advanced Topics
Remote Builds
Nix supports remote builds, where a local Nix installation can forward
Nix builds to other machines. This allows multiple builds to be
performed in parallel and allows Nix to perform multi-platform builds in
a semi-transparent way. For instance, if you perform a build for a
x86_64-darwin
on an i686-linux
machine, Nix can automatically
forward the build to a x86_64-darwin
machine, if available.
To forward a build to a remote machine, it’s required that the remote machine is accessible via SSH and that it has Nix installed. You can test whether connecting to the remote Nix instance works, e.g.
$ nix ping-store --store ssh://mac
will try to connect to the machine named mac
. It is possible to
specify an SSH identity file as part of the remote store URI, e.g.
$ nix ping-store --store ssh://mac?ssh-key=/home/alice/my-key
Since builds should be non-interactive, the key should not have a
passphrase. Alternatively, you can load identities ahead of time into
ssh-agent
or gpg-agent
.
If you get the error
bash: nix-store: command not founderror: cannot connect to 'mac'
then you need to ensure that the PATH
of non-interactive login shells
contains Nix.
If you are building via the Nix daemon, it is the Nix daemon user
account (that is, root
) that should have SSH access to the remote
machine. If you can’t or don’t want to configure root
to be able to
access to remote machine, you can use a private Nix store instead by
passing e.g. --store ~/my-nix
.
The list of remote machines can be specified on the command line or in
the Nix configuration file. The former is convenient for testing. For
example, the following command allows you to build a derivation for
x86_64-darwin
on a Linux machine:
$ unameLinux$ nix build \'(with import <nixpkgs> { system = "x86_64-darwin"; }; runCommand "foo" {} "uname > $out")' \--builders 'ssh://mac x86_64-darwin'[1/0/1 built, 0.0 MiB DL] building foo on ssh://mac$ cat ./resultDarwin
It is possible to specify multiple builders separated by a semicolon or a newline, e.g.
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
Each machine specification consists of the following elements, separated
by spaces. Only the first element is required. To leave a field at its
default, set it to -
.
The URI of the remote store in the format
ssh://[username@]hostname
, e.g.ssh://nix@mac
orssh://mac
. For backward compatibility,ssh://
may be omitted. The hostname may be an alias defined in your~/.ssh/config
.A comma-separated list of Nix platform type identifiers, such as
x86_64-darwin
. It is possible for a machine to support multiple platform types, e.g.,i686-linux,x86_64-linux
. If omitted, this defaults to the local platform type.The SSH identity file to be used to log in to the remote machine. If omitted, SSH will use its regular identities.
The maximum number of builds that Nix will execute in parallel on the machine. Typically this should be equal to the number of CPU cores. For instance, the machine
itchy
in the example will execute up to 8 builds in parallel.The “speed factor”, indicating the relative speed of the machine. If there are multiple machines of the right type, Nix will prefer the fastest, taking load into account.
A comma-separated list of supported features. If a derivation has the
requiredSystemFeatures
attribute, then Nix will only perform the derivation on a machine that has the specified features. For instance, the attributerequiredSystemFeatures = [ "kvm" ];will cause the build to be performed on a machine that has the
kvm
feature.A comma-separated list of mandatory features. A machine will only be used to build a derivation if all of the machine’s mandatory features appear in the derivation’s
requiredSystemFeatures
attribute..
For example, the machine specification
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvmnix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark
specifies several machines that can perform i686-linux
builds.
However, poochie
will only do builds that have the attribute
requiredSystemFeatures = [ "benchmark" ];
or
requiredSystemFeatures = [ "benchmark" "kvm" ];
itchy
cannot do builds that require kvm
, but scratchy
does support
such builds. For regular builds, itchy
will be preferred over
scratchy
because it has a higher speed factor.
Remote builders can also be configured in nix.conf
, e.g.
builders = ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd
Finally, remote builders can be configured in a separate configuration
file included in builders
via the syntax @file
. For example,
builders = @/etc/nix/machines
causes the list of machines in /etc/nix/machines
to be included. (This
is the default.)
If you want the builders to use caches, you likely want to set the
option builders-use-substitutes
in
your local nix.conf
.
To build only on remote builders and disable building on the local
machine, you can use the option --max-jobs 0
.
Tuning Cores and Jobs
Nix has two relevant settings with regards to how your CPU cores will be utilized: varlistentry_title and varlistentry_title. This chapter will talk about what they are, how they interact, and their configuration trade-offs.
varlistentry_title Dictates how many separate derivations will be built at the same time. If you set this to zero, the local machine will do no builds. Nix will still substitute from binary caches, and build remotely if remote builders are configured.
varlistentry_title Suggests how many cores each derivation should use. Similar to
make -j
.
The varlistentry_title setting determines the value of
NIX_BUILD_CORES
. NIX_BUILD_CORES
is equal to
varlistentry_title, unless
varlistentry_title equals 0
, in which case
NIX_BUILD_CORES
will be the total number of cores in the system.
The maximum number of consumed cores is a simple multiplication,
varlistentry_title * NIX_BUILD_CORES
.
The balance on how to set these two independent variables depends upon each builder's workload and hardware. Here are a few example scenarios on a machine with 24 cores:
varlistentry_title | varlistentry_title |
| Maximum Processes | Result |
---|---|---|---|---|
1 | 24 | 24 | 24 | One derivation will be built at a time, each one can use 24 cores. Undersold if a job can’t use 24 cores. |
4 | 6 | 6 | 24 | Four derivations will be built at once, each given access to six cores. |
12 | 6 | 6 | 72 | 12 derivations will be built at once, each given access to six cores. This configuration is over-sold. If all 12 derivations being built simultaneously try to use all six cores, the machine's performance will be degraded due to extensive context switching between the 12 builds. |
24 | 1 | 1 | 24 | 24 derivations can build at the same time, each using a single core. Never oversold, but derivations which require many cores will be very slow to compile. |
24 | 0 | 24 | 576 | 24 derivations can build at the same time, each using all the available cores of the machine. Very likely to be oversold, and very likely to suffer context switches. |
It is up to the derivations' build script to respect host's requested
cores-per-build by following the value of the NIX_BUILD_CORES
environment variable.
Verifying Build Reproducibility with diff-hook
Specify a program with Nix's varlistentry_title to compare build results when two builds produce different results. Note: this hook is only executed if the results are not the same, this hook is not used for determining if the results are the same.
For purposes of demonstration, we'll use the following Nix file,
deterministic.nix
for testing:
letinherit (import <nixpkgs> {}) runCommand;in {stable = runCommand "stable" {} ''touch $out'';unstable = runCommand "unstable" {} ''echo $RANDOM > $out'';}
Additionally, nix.conf
contains:
diff-hook = /etc/nix/my-diff-hookrun-diff-hook = true
where /etc/nix/my-diff-hook
is an executable file containing:
#!/bin/shexec >&2echo "For derivation $3:"/run/current-system/sw/bin/diff -r "$1" "$2"
The diff hook is executed by the same user and group who ran the build. However, the diff hook does not have write access to the store path just built.
Spot-Checking Build Determinism
Verify a path which already exists in the Nix store by passing --check
to the build command.
If the build passes and is deterministic, Nix will exit with a status code of 0:
$ nix-build ./deterministic.nix -A stablethese derivations will be built:/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drvbuilding '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'.../nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable$ nix-build ./deterministic.nix -A stable --checkchecking outputs of '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'.../nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
If the build is not deterministic, Nix will exit with a status code of 1:
$ nix-build ./deterministic.nix -A unstablethese derivations will be built:/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drvbuilding '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'.../nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable$ nix-build ./deterministic.nix -A unstable --checkchecking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs
In the Nix daemon's log, we will now see:
For derivation /nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv:1c1< 8108---> 30204
Using --check
with --keep-failed
will cause Nix to keep the second
build's output in a special, .check
path:
$ nix-build ./deterministic.nix -A unstable --check --keep-failedchecking outputs of '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...note: keeping build directory '/tmp/nix-build-unstable.drv-0'error: derivation '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv' may not be deterministic: output '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable' differs from '/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check'
In particular, notice the
/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check
output. Nix
has copied the build results to that directory where you can examine it.
.check
paths are not registered store pathsCheck paths are not protected against garbage collection, and this path will be deleted on the next garbage collection.
The path is guaranteed to be alive for the duration of varlistentry_title's execution, but may be deleted any time after.
If the comparison is performed as part of automated tooling, please use the diff-hook or author your tooling to handle the case where the build was not deterministic and also a check path does not exist.
--check
is only usable if the derivation has been built on the system
already. If the derivation has not been built Nix will fail with the
error:
error: some outputs of '/nix/store/hzi1h60z2qf0nb85iwnpvrai3j2w7rr6-unstable.drv' are not valid, so checking is not possible
Run the build without --check
, and then try with --check
again.
Automatic and Optionally Enforced Determinism Verification
Automatically verify every build at build time by executing the build multiple times.
Setting varlistentry_title and
varlistentry_title in your nix.conf
permits the automated verification of every build Nix performs.
The following configuration will run each build three times, and will require the build to be deterministic:
enforce-determinism = truerepeat = 2
Setting varlistentry_title to false as in the following configuration will run the build multiple times, execute the build hook, but will allow the build to succeed even if it does not build reproducibly:
enforce-determinism = falserepeat = 1
An example output of this configuration:
$ nix-build ./test.nix -A unstablethese derivations will be built:/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drvbuilding '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 1/2)...building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 2/2)...output '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable' of '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' differs from '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable.check' from previous round/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable
Using the post-build-hook
Implementation Caveats
Here we use the post-build hook to upload to a binary cache. This is a simple and working example, but it is not suitable for all use cases.
The post build hook program runs after each executed build, and blocks the build loop. The build loop exits if the hook program fails.
Concretely, this implementation will make Nix slow or unusable when the internet is slow or unreliable.
A more advanced implementation might pass the store paths to a user-supplied daemon or queue for processing the store paths outside of the build loop.
Prerequisites
This tutorial assumes you have configured an S3-compatible binary cache
according to the instructions at Authenticated Writes to your
S3-compatible binary cache,
and that the root
user's default AWS profile can upload to the bucket.
Set up a Signing Key
Use nix-store --generate-binary-cache-key
to create our public and
private signing keys. We will sign paths with the private key, and
distribute the public key for verifying the authenticity of the paths.
# nix-store --generate-binary-cache-key example-nix-cache-1 /etc/nix/key.private /etc/nix/key.public# cat /etc/nix/key.publicexample-nix-cache-1:1/cKDz3QCCOmwcztD2eV6Coggp6rqc9DGjWv7C0G+rM=
Then, add the public key and the cache URL to your nix.conf
's
varlistentry_title and
varlistentry_title like:
substituters = https://cache.nixos.org/ s3://example-nix-cachetrusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= example-nix-cache-1:1/cKDz3QCCOmwcztD2eV6Coggp6rqc9DGjWv7C0G+rM=
We will restart the Nix daemon in a later step.
Implementing the build hook
Write the following script to /etc/nix/upload-to-cache.sh
:
#!/bin/shset -euset -f # disable globbingexport IFS=' 'echo "Signing paths" $OUT_PATHSnix sign-paths --key-file /etc/nix/key.private $OUT_PATHSecho "Uploading paths" $OUT_PATHSexec nix copy --to 's3://example-nix-cache' $OUT_PATHS
Should $OUT_PATHS
be quoted?
The $OUT_PATHS
variable is a space-separated list of Nix store paths.
In this case, we expect and want the shell to perform word splitting to
make each output path its own argument to nix sign-paths
. Nix
guarantees the paths will not contain any spaces, however a store path
might contain glob characters. The set -f
disables globbing in the
shell.
Then make sure the hook program is executable by the root
user:
# chmod +x /etc/nix/upload-to-cache.sh
Updating Nix Configuration
Edit /etc/nix/nix.conf
to run our hook, by adding the following
configuration snippet at the end:
post-build-hook = /etc/nix/upload-to-cache.sh
Then, restart the nix-daemon
.
Testing
Build any derivation, for example:
$ nix-build -E '(import <nixpkgs> {}).writeText "example" (builtins.toString builtins.currentTime)'these derivations will be built:/nix/store/s4pnfbkalzy5qz57qs6yybna8wylkig6-example.drvbuilding '/nix/store/s4pnfbkalzy5qz57qs6yybna8wylkig6-example.drv'...running post-build-hook '/home/grahamc/projects/github.com/NixOS/nix/post-hook.sh'...post-build-hook: Signing paths /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-examplepost-build-hook: Uploading paths /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example/nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example
Then delete the path from the store, and try substituting it from the binary cache:
$ rm ./result$ nix-store --delete /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example
Now, copy the path back from the cache:
$ nix-store --realise /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-examplecopying path '/nix/store/m8bmqwrch6l3h8s0k3d673xpmipcdpsa-example from 's3://example-nix-cache'...warning: you did not specify '--add-root'; the result might be removed by the garbage collector/nix/store/m8bmqwrch6l3h8s0k3d673xpmipcdpsa-example
Conclusion
We now have a Nix installation configured to automatically sign and upload every local build to a remote binary cache.
Before deploying this to production, be sure to consider the implementation caveats in Implementation Caveats.