Reproducible Dev Shells with Nix Flakes
One flake.nix, one nix develop, and every teammate gets the exact same toolchain.
What you'll build
A single flake.nix committed to your project root that gives every teammate — and CI — a bit-for-bit identical toolchain with one command: nix develop. We'll pin Node.js 24, Python 3, and jq, but the pattern works for any of nixpkgs' ~100,000 packages.
Prerequisites
- macOS or Linux (on Windows, use WSL2 and follow the Linux path).
sudoaccess, plusgitinstalled.- Verified against Nix 2.34.5 (current stable) and the
nixos-26.05nixpkgs release. Any Nix ≥ 2.18 behaves the same for everything below.
Flakes are still formally an experimental feature, but the file format is stable, and they've been the de facto standard for dev shells for years. We'll enable them in Step 2.
Step 1: Install Nix
On Linux (systemd, SELinux disabled):
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | sh -s -- --daemon
On macOS:
curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | sh
Open a new terminal afterward — the installer edits your shell profile — and confirm:
nix --version
# nix (Nix) 2.34.5
Step 2: Enable flakes
The nix develop command and flakes are gated behind a feature flag. Turn both on once, user-wide:
mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf
(For a machine-wide setting, put the same line in /etc/nix/nix.conf instead.)
Step 3: Write the flake
In your project root, create flake.nix:
{
description = "Dev shell for acme-api";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems
(system: f nixpkgs.legacyPackages.${system});
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs_24
python3
jq
];
shellHook = ''
echo "acme-api shell — node $(node --version), $(python3 --version)"
'';
};
});
};
}
Three things worth knowing: inputs.nixpkgs.url pins the package set to the 26.05 stable branch (swap in nixos-unstable if you want newer packages); the forAllSystems helper defines the same shell for Intel/ARM Linux and Mac so nobody's architecture is left out; and packages lists what lands on your PATH. Find attribute names for other tools at search.nixos.org.
If the project is a git repo, flakes only see files git knows about, so:
git add flake.nix
Step 4: Enter the shell
nix develop
The first run downloads the pinned packages (a few minutes), then drops you into a bash shell where node, python3, and jq are the flake's versions — regardless of what's installed on the host. It also writes flake.lock, which records the exact nixpkgs commit you resolved. That file is the whole trick:
git add flake.lock
git commit -m "Add reproducible dev shell"
Anyone who clones the repo and runs nix develop now gets your toolchain, not "whatever the branch points at today." To deliberately bump everything later, run nix flake update and commit the new lock file.
Verify it works
From a teammate's perspective — fresh clone, then:
$ nix develop
acme-api shell — node v24.18.1, Python 3.13.14
Your patch versions depend on the locked nixpkgs commit (those are current for nixos-26.05 at the time of writing) — the point is that everyone sees the same ones. You can also run a single command inside the shell without staying in it, which is exactly what you'll do in CI:
$ nix develop --command node --version
v24.18.1
Type exit to leave; your host environment is untouched.
Troubleshooting
error: experimental Nix feature 'nix-command' is disabled; add '--extra-experimental-features nix-command' to enable it
Step 2 didn't take. Check that ~/.config/nix/nix.conf contains the experimental-features line with no typos — it's read per-invocation, so no daemon restart is needed.
error: getting status of '/nix/store/…-source/flake.nix': No such file or directory
You're in a git repo and flake.nix is untracked; Nix copies only git-tracked files. Fix: git add flake.nix (staging is enough, no commit needed). The same applies to any new file the flake references.
zsh: command not found: nix right after installing
The current terminal predates the profile changes. Open a new terminal, or run . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh.
error: attribute 'nodejs_24' missing
Your lock file points at a nixpkgs snapshot from before that package existed. Run nix flake update, or check the attribute name for your pinned branch on search.nixos.org.
Next steps
Add project env vars and setup commands to shellHook (database URLs, npm install on entry). Install direnv with nix-direnv so the shell activates automatically on cd, instead of you typing nix develop. In CI, nix develop --command make test reuses the identical environment. When you outgrow one shell, define extras alongside default (e.g. devShells.<system>.ci) and enter them with nix develop .#ci. The nix.dev tutorials are the best structured path deeper into the ecosystem.
Sources & further reading
- Download Nix — nixos.org
- Flakes - Official NixOS Wiki — wiki.nixos.org
- Declarative shell environments — nix.dev
- NixOS 26.05 released — nixos.org
- nix flake commands fail when flake.nix is untracked in git — github.com
Lenn writes about cloud platforms, Kubernetes internals, and the infrastructure decisions that quietly make or break engineering organizations. Based in Berlin's vibrant tech scene, they have a talent for turning dense platform-engineering topics into prose that people actually finish reading.
Discussion 0
No comments yet
Be the first to weigh in.