references and closures

how nix tracks what depends on what, and what that means for your builds.

chapter 1 showed how store paths are computed from inputs and how the build sandbox keeps them honest. but a built package does not exist in isolation. at runtime it links against libraries, calls interpreters, reads data files. all of those live at other store paths.

nix needs to know which paths a built package actually uses at runtime. not which ones the derivation declared as build inputs: which ones ended up embedded in the output itself.

the reference scanner

after a build completes, nix scans every file in the output. it searches for 32-character strings from nix's base-32 alphabet. any sequence that matches a known store path hash is a reference. that path's hash was found in the output, so the output needs that path at runtime.

the scanner is not parsing ELF headers or interpreting shebangs. it is a string search. it finds the hash, records the dependency.

every path a built package references gets stored in nix's database for that output path.

closures

references compose. if curl references openssl, and openssl references glibc, then anything that needs curl also needs openssl and glibc.

the complete transitive set of a path and everything it references, recursively, is its closure.

824ww67s...curl-8.18.0
refs
j1wa34bb...openssl-3.6.1
refs
km4g87jx...glibc-2.40-218

curl-8.18.0 has 14 direct references and a closure of 22 paths. about 60 MiB total. that is what ships when you deploy curl with nix.

what's next

references explains how the scanner works and what build-time versus runtime dependencies actually mean.

closures and the garbage collector covers how nix decides what to keep and what to delete.

self-references is the odd case: packages that contain their own store path in their output.