The /nix/store Anatomy
The /nix/store Anatomy
Everything Nix builds, downloads, or installs lands in /nix/store. The boring default is: treat the store as a read-only database of paths named by hash, and never move, rename, or chmod anything inside it.
Mental model
Every store path looks like this:
/nix/store/<hash>-<name>-<version>
Example:
/nix/store/z1k947f6y788sfaivs931h26hsvb3506-ripgrep-14.1.0
| Piece | Meaning |
|---|---|
| Hash | 32 characters of Nix base32. Identity of this path. |
| Name + version | Human label. Not identity. Two ripgrep-14.1.0 builds can coexist. |
| The directory | A complete artifact: bin/, lib/, share/, or a single file. |
The hash is computed from the inputs of the derivation (the usual case) or from the contents of the output (fixed-output and content-addressed paths). Either way, a different hash is a different path. Nothing is overwritten.
Permissions are read-only. Timestamps are the Unix epoch (Jan 1 1970) so two machines producing the same bytes do not disagree about mtime.
/nix
├── store/ immutable artifacts
└── var/nix/
├── db/ SQLite index of those artifacts
├── profiles/ generations (GC roots)
└── gcroots/ extra roots (result, direnv, …)
Your shell’s rg is not “the ripgrep in /usr/bin”. It is a symlink into a specific store path.
Worked examples
Case 1: Follow a binary into the store
which rg
ls -l $(which rg)
ls -ld $(dirname $(which rg))/..Output (hashes will differ on your machine):
/run/current-system/sw/bin/rg
lrwxrwxrwx 1 root root 68 Jan 1 1970 /run/current-system/sw/bin/rg -> /nix/store/z1k947f6y788sfaivs931h26hsvb3506-ripgrep-14.1.0/bin/rg
dr-xr-xr-x 1 root root 4096 Jan 1 1970 /nix/store/z1k947f6y788sfaivs931h26hsvb3506-ripgrep-14.1.0
On a Nix-on-Linux user profile the first hop may be ~/.nix-profile/bin/rg instead of /run/current-system. The last hop is always /nix/store/….
Case 2: Read-only and epoch timestamps
ls -ld $(readlink -f $(which nix))Output:
-r-xr-xr-x 1 root root 15728640 Jan 1 1970 /nix/store/7vqw9388aaaaaaaaaaaaaaaaaaaaaaaa-nix-2.35.2/bin/nix
- Mode
r-xr-xr-x(orr-xr-xr-xon the directoryr-xr-xr-x): no write bit. - Date
Jan 1 1970: not “this was built at the epoch”; the timestamp is normalised so it is not an input to the next hash.
Try to touch it:
touch $(readlink -f $(which nix))Output:
touch: cannot touch '/nix/store/…-nix-2.35.2/bin/nix': Permission denied
Case 3: Two versions, no collision
nix build nixpkgs#hello --out-link hello-a
ls -ld hello-aOutput:
lrwxrwxrwx 1 deskadmin deskadmin 55 Jan 1 12:00 hello-a -> /nix/store/…-hello-2.12.1
hello-a is a GC root in the current directory (the result-style symlink). The store path is the real artifact. Installing a different nixpkgs revision of hello produces a second directory next to the first. Both stay until nothing roots them.
Case 4: What is not in the store
ls /usr/bin/rg 2>&1 || true
echo $PATH | tr ':' '\n' | headOn NixOS, /usr/bin is nearly empty (env, maybe sh). Tools live under /run/current-system/sw/bin and /nix/store. On Nix-on-Linux, /usr/bin still has the host distro; Nix tools are extra entries on PATH. Mixing them is how “it works in my shell” happens. Prefer the store path.
Case 5: Name a path without guessing the hash
nix path-info nixpkgs#helloOutput:
/nix/store/…-hello-2.12.1
nix path-info -Sh nixpkgs#hello adds closure size. You do not construct store paths by concatenating strings in a shell script.
The trap
The trap is sudo chmod -R u+w /nix/store/… so you can patch a file. The path’s hash promised those bytes. After you write to it, every later build that trusted that path is lying. The daemon may also refuse to register the path on --verify.
If a binary is wrong, rebuild it with different inputs. You get a new hash. The old path stays until GC.
A related trap: copying a store path to another machine with scp and dropping it under /nix/store by hand. Use nix copy so the database is updated. A directory that is not registered in db.sqlite is invisible to Nix and eligible for nothing useful.
The boring rule
- All Nix artifacts live under
/nix/store/<hash>-<name>. - Follow
whichuntil you see/nix/store. That is the real path. - Never
rm,mv,chmod, ortouchinside the store. - Use
nix path-info/nix-store -qto name paths. Do not paste hashes from memory. - Treat
resultand profile symlinks as pointers, not as the software.
Try this
- Run
readlink -f $(which bash)(or$(which sh)) and count how many symlink hops precede/nix/store. ls /nix/store | wc -l— that is how many artifacts this machine currently holds. Run it again afternix run nixpkgs#cowsay -- hiand see the count move.stat $(readlink -f $(which nix))and confirmAccess: (0555)(or similar, no write) and a 1970 timestamp.- Try
echo x >> $(readlink -f $(which nix))and save the exact error. That error is the product.