Pinning
Pinning keeps your developer environment reproducible. Each
input in devenv.yaml is resolved to an exact revision and
stored in devenv.lock — the same idea as a lockfile in other
ecosystems (package-lock, poetry.lock, composer.lock, and so on).
How it works
You declare inputs in devenv.yaml:
inputs: nixpkgs: url: github:cachix/devenv-nixpkgs/rolling
devenv.yaml
devenv writes the resolved revisions into devenv.lock. After that,
everyone who uses the same lockfile gets the same dependency versions —
until someone runs devenv update.
You do not need a separate “create lock” step. The lock is created or updated when you use devenv on the project.
Viewing pins
$ cat devenv.lock
Terminal window
devenv info also shows locked inputs.
Pinning an individual package version
Pinning a nixpkgs input selects one revision of the whole package collection. To select an exact version of an individual package instead, add the nixpkgs-multiverse input:
$ devenv inputs add nixpkgs-multiverse github:fzakaria/nixpkgs-multiverse
Terminal window
Then select packages by attribute and version in devenv.nix:
{ multiverse, ... }:
{ packages = [ multiverse.cmake."3.16.5" multiverse.bun."0.7.0" ];}
devenv.nix
devenv.lock pins the Multiverse revision, and its version index
selects the corresponding historical nixpkgs revision for each package.
Those historical revisions are fetched only when referenced. Running
devenv update nixpkgs-multiverse intentionally refreshes the version
index; until then, the selected packages remain reproducible. See
Installing a specific
version for usage details and
limitations.
Each pin resolves independently, and multiple pins may require multiple
nixpkgs revisions to be fetched and evaluated, adding overhead.
multiverse.pins resolves the entire set using the fewest revisions
possible.
{ multiverse, ... }:
{ packages = multiverse.pins { cmake = "3.26.4"; bun = "0.7.0"; };}
devenv.nix
See Pinning several packages for what minimizing decides, and the fewest nixpkgs for why it is minimal.
Pinning a nixpkgs revision
Use ?rev= in the input URL for an exact commit:
inputs: nixpkgs-stable: url: github:NixOS/nixpkgs/ac62194c3917d5f474c1a844b6fd6da2db95077d
devenv.yaml
Use ?ref= for a branch or tag (for example the NixOS 25.05 release
branch):
inputs: nixpkgs-stable: url: github:NixOS/nixpkgs?ref=nixos-25.05
devenv.yaml
With ?rev=…, that commit is fixed in the declaration. With only a
branch or tag (?ref=… or a path like …/nixos-25.05), the lockfile
freezes whatever commit was current when the lock was last written; it
does not keep moving on every command.
Updating pins
devenv update is how you intentionally refresh the lockfile — same
role as npm update, poetry update, or similar. Day-to-day commands
keep using the revisions already in devenv.lock. They do not pull
newer commits from a floating branch just because upstream moved.
Refresh every input’s locked revision (where the URL still points at a branch or tag):
$ devenv update
Terminal window
Refresh a single input by name:
$ devenv update nixpkgs-stable
Terminal window
That re-resolves the input from its URL and rewrites devenv.lock if
the result changed. Commit the new lock so the team and CI pick up the
same pins.
Adding inputs from the CLI
$ devenv inputs add nixpkgs-stable github:NixOS/nixpkgs/nixos-25.05$ devenv inputs add my-input github:org/repo --follows nixpkgs
Terminal window
This updates devenv.yaml. The lock is refreshed the next time you run
a devenv command on the project.
Commit the lockfile
Commit devenv.lock to version control. It is what makes the
environment reproducible across machines.
If the lockfile is missing, devenv creates one by resolving inputs. Without a shared lock, floating branches or tags can resolve to different commits on different machines.
Last updated Oct 08, 2026