Skip to content
Latchkey

nix-shell: Reproducible Toolchains in CI

nix-shell builds an environment with the exact packages from a shell.nix (or -p list) and, with --run, executes a command inside it non-interactively.

Nix gives bit-for-bit reproducible toolchains: pin nixpkgs and every runner gets identical compilers and tools. In CI you use nix-shell --run (or the newer nix develop) so it never drops into an interactive prompt.

What it does

nix-shell evaluates a shell.nix (or ad-hoc -p package list) and enters a shell where exactly those packages are on PATH, built from a pinned nixpkgs. The same inputs produce the same closure on any machine, which is why it is used for reproducible CI environments.

Common usage

bash
# ad-hoc tools
nix-shell -p nodejs_20 python312 --run "node -v && python --version"
# from a shell.nix in the repo, run the test suite inside the env
nix-shell --run "npm ci && npm test"
# flakes-based equivalent
nix develop --command bash -c "cargo test"

Options

Command / flagWhat it does
nix-shell -p <pkgs>Ad-hoc env with the listed packages
nix-shell --run "<cmd>"Run a command in the env, non-interactive
nix-shell --pureDrop the ambient environment for isolation
shell.nixDeclarative env definition in the repo
nix developFlakes-based dev shell (needs experimental features)
-I nixpkgs=<pin>Pin the nixpkgs revision

In CI

Always use --run (or nix develop --command) so the job does not block on an interactive shell. Pin nixpkgs (a flake.lock or a fixed -I nixpkgs= URL) so results are reproducible across runs. --pure maximizes isolation. Enable the binary cache and cache /nix/store to avoid rebuilding.

Common errors in CI

"error: experimental Nix feature 'nix-command' is disabled" or "'flakes' is disabled" when using nix develop means you must pass --extra-experimental-features "nix-command flakes" or set it in nix.conf. "nix-shell: command not found" means Nix is not installed/sourced (source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh). A hanging job usually means you ran nix-shell without --run and it opened an interactive shell.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →