the build sandbox
how nix isolates builds from the rest of the system.
when nix runs a build, the builder does not get your environment. no HOME, no PATH from your shell, no network access, no /etc. it starts from nothing and gets only what you declared.
this is how
linux: namespaces
on linux, nix calls clone() with new mount, network, UTS, IPC, and PID namespaces. the build runs as a dedicated nixbld user with no special privileges.
from inside the build, it looks like a fresh machine.
what the builder can see
/nix/store: declared inputs only, bind-mounted read-only from the host store. the builder can read any path it was given. it cannot see paths it was not given, even though they sit in the same directory on the host.
/tmp: a fresh tmpfs. writable, empty, wiped when the build finishes.
/dev: minimal devices only: full, null, random, tty, urandom, zero. no block devices, no access to host hardware.
network: loopback interface only (lo, 127.0.0.1). no external connectivity. a curl https://example.com inside a build will fail.
what the builder cannot see
- your home directory
/etc(or a minimal synthetic version with just enough for the build to run:/etc/passwdwith thenixblduser,/etc/group)- the internet
- any environment variable from your shell
what nix sets for the builder
instead of inheriting your shell environment, nix injects a small fixed set:
| variable | value |
|---|---|
$out |
output path. write your build here |
$src |
source path in /nix/store |
PATH |
constructed from bin/ dirs of declared deps |
HOME |
/homeless-shelter |
NIX_BUILD_TOP |
the build directory |
TMPDIR |
/tmp |
HOME=/homeless-shelter is deliberate. any tool that tries to read ~/.config/something gets a path that does not exist. if your build depends on a dotfile, it breaks loud and early.
the contract
write your output to $out. that directory becomes the store path.
a standard nix build script (default-builder.sh) runs unpackPhase, configurePhase, buildPhase, installPhase: each one writing into $out by the end. it is just shell.
when the build finishes, nix registers $out into the store, makes it read-only, and records it as the output of the derivation.
what a sandbox violation looks like
each of these fails immediately, with an error that points directly at what's missing:
- a configure script looks up
/etc/ssl/certsfor CA bundles and finds nothing. the build fails with a TLS error or a missing-file error. - a build step tries to fetch a dependency at compile time with curl. it cannot reach the internet. connection refused.
- a tool writes to
~/cache/something.HOMEis/homeless-shelter, which does not exist. permission denied or file not found. - a test suite tries to bind to a network port on an external interface. the interface does not exist inside the sandbox.
all of these are features. they tell you the build has undeclared dependencies. fix the derivation, not the sandbox.
macOS: sandbox-exec
macOS does not have linux namespaces. nix uses Apple's sandbox API (libsandbox) with a generated Scheme profile instead.
same goal: block filesystem access outside declared paths, no network, restricted device access. the surface area differs slightly. some builds need additional allowances for macOS-specific paths. the principle is the same.
the filesystem a build sees
why this matters
a build that passes in nix's sandbox will produce the same result on any other machine with the same inputs. the sandbox is what makes that true.
if nix builds could silently read from your home directory or phone home during compilation, store path hashes would be meaningless. two people building the same derivation could get different binaries with the same path. the whole model collapses.
the sandbox is not a security boundary. it is a correctness boundary.