references
how nix scans build outputs to discover runtime dependencies.
how nix scans build outputs to discover runtime dependencies.
a derivation lists its inputDrvs: every other derivation needed to build it: the compiler, the headers, the build tools. all declared upfront so nix can compute the hash and prepare the sandbox.
inputDrvs are build-time. once the build finishes, what matters is which paths the output actually uses at runtime. a statically compiled binary needs nothing at runtime. a dynamically linked one needs its shared libraries. a Python script needs its interpreter. the derivation alone cannot answer which case you are in.
after a build completes, nix scans the output tree. it reads every file (compiled binaries, shell scripts, config files, symlinks) and searches for 32-character sequences drawn from nix's base-32 alphabet:
0123456789abcdfghijklmnpqrsvwxyz
no e, o, t, or u. same alphabet used in store path hashes.
any such sequence that matches a known store path hash is recorded as a reference. the full set is written to nix's database (/nix/var/nix/db/db.sqlite) for that output path.
$ nix-store -q --references /nix/store/xhp149abalfmj232yjhgbqaw281ba8np-curl-8.18.0
/nix/store/hxcmad417fd8ql9ylx96xpak7da06yiv-libidn2-2.3.8
/nix/store/vr7ds8vwbl2fz7pr221d5y0f8n9a5wda-glibc-2.40-218
/nix/store/3qkwgkbjisvm8z1g826bvqfg6j4cr35x-nghttp2-1.67.1-lib
/nix/store/8dj39rr9xp8qpl3myqj8i04a8pwhyl60-openssl-3.6.1
/nix/store/b3vi2i22167nhmrnl85ks3xpzl8bjj56-krb5-1.22.1-lib
/nix/store/b7wbagl6c1xr79r6hbcc7fznjjibc8mr-libpsl-0.21.5
/nix/store/xdxxfabbd8w0dadijsd8rkgvnhpn3rkf-zlib-1.3.1
/nix/store/chqzzvliv0mldn2n6b96aivcmhvc0hb8-libssh2-1.11.1
/nix/store/gwy8kliqcqspz7r56y6hn2a0k28r5hak-zstd-1.5.7
/nix/store/ka7244lkykijn9k6p4c8a2acz8yz9pnd-brotli-1.1.0-lib
/nix/store/mjwqy9bka7zma90b6q6vg4lc51wzrwda-ngtcp2-1.17.0
/nix/store/pdp6hrs98pkfymhg7869pzggw2xizskc-nghttp3-1.12.0
/nix/store/xhp149abalfmj232yjhgbqaw281ba8np-curl-8.18.0
13 direct references. the last one is curl referencing itself, covered in self-references.
inputDrvs and references are not the same set.
a header-only dependency (a C++ template library, for example) is in inputDrvs (required to compile) but leaves no trace in the output. it will not appear in the reference set. nix does not require it to deploy or run the package.
conversely, if a store path hash appears anywhere in the output, it becomes a reference, regardless of how it got there. a hardcoded path in a config file, a path embedded in a shell script, a path written into a binary's RPATH: all are found and recorded.
| inputDrvs | references | |
|---|---|---|
| declared in | derivation | inferred from output |
| affects | store path hash | runtime closure |
| required for | building | running |
nix stores reference data at build time and answers queries from its database:
# direct references of a path
$ nix-store -q --references /nix/store/...-curl-8.18.0
# reverse: what references this path
$ nix-store -q --referrers /nix/store/...-openssl-3.6.1
# json, with the new cli
$ nix path-info --json nixpkgs#curl | jq '.[].references'
--references shows direct references only. to see the full transitive set (everything the package needs), use --requisites or nix path-info --recursive. that is covered in closures and the garbage collector.
the scanner doesn't know what an ELF binary is. it reads bytes and looks for 32-character sequences from the nix base-32 alphabet. that's the entire algorithm.
if a hash shows up in the output, a build step put it there. the alphabet is specific enough that it doesn't happen by accident. and since the sandbox only gave the builder access to declared inputs, those are the only hashes that could end up in the output.
nix does not filter the results. if the hash is present, it is a reference, full stop. if you actually don't need a path at runtime but its hash ended up in some output file anyway, you can strip it explicitly with removeReferencesTo. the default is to keep everything the scanner finds.