Fixed-Output Derivations vs Input-Addressed Derivations
FODs verify outputs but don't compute paths from them like true content-addressed systems do.

A derivation in Nix is a build task: a spec that names its inputs, the builder to run, and the outputs it should produce. Every output lands in the Nix store at a path stamped with a hash, something like /nix/store/lz9gfg6iybsh0hiignpk55w99a3bj4vb-hello-2.12.1. The question that matters, and the one this piece is about, is what that hash actually covers. It's not a universal answer. It depends entirely on which addressing model the derivation uses, and that choice shapes everything downstream: what the store path proves, what the builder is allowed to touch, and what breaks when something upstream changes.
Two models are stable in Nix without turning on experimental features: input-addressed derivations, which are the default, and fixed-output derivations (FODs), a controlled exception carved out for one specific problem. A third model, floating content-addressed derivations, exists behind the ca-derivations experimental flag, and it's worth naming early so the other two have somewhere to sit in context. Per the Nix reference manuals across versions 2.28, 2.29, and 2.32, the addressing type is uniform across a derivation's outputs: you don't get to mix schemes within one derivation. That constraint alone tells you this isn't a cosmetic setting. It's a guarantee you're choosing to make, or choosing not to.
How input-addressed derivations work and what the hash actually covers
In an input-addressed derivation, the store path comes from the contents of the .drv file, not from the bytes the build eventually produces. The .drv file records source paths, references to other derivations, build flags, environment variables, all of it. Change any of those and the hash changes, and a new store path gets generated before a single build step runs.
That's the mechanical detail people gloss over: the .drv file already contains the output paths. Nix knows where the output will live before the build has produced anything. Calling the output "floating" only makes sense in the narrow sense that it hasn't been built yet, not that its location is undetermined.
This is also why input-addressed builds have to be pure. No network access, full stop. The store path is a promise that a given set of inputs always yields the same output, and an impure build (one that could fetch something different each time, at the same fixed path) would break that promise at the root. The whole integrity model of the store depends on that path staying trustworthy.
The tradeoff shows up as a cascade. Because .drv files reference other .drv files by path, a change anywhere in the graph ripples forward. Patch OpenSSL, and its .drv hash changes, so its output path changes, so every derivation that depends on it gets a new hash and a new path, and so on through the graph. This happens even for changes that couldn't possibly matter: a comment in source, a URL that resolves to byte-for-byte identical content. The hash doesn't know or care that nothing meaningful changed. A large NixOS binary cache exists largely to absorb this. So does the build infrastructure behind NixOS, which spends real compute re-deriving huge swaths of the dependency graph every time a single byte of glibc shifts.
What you get in exchange is a specific, narrow guarantee: given the same inputs, you get the same build environment, every time. That's a promise about process, running forward from inputs to outcome, not a promise about the resulting bytes running backward toward truth. For fully sandboxed builds compiling from source, where every input is already sitting in the store and there's no reason to touch the network, this model is exactly right and nothing else is needed.
Why fixed-output derivations exist: the network access problem
Nix packaging keeps running into the same wall. Tools like npm, Cargo, Go's module system, Maven, and Gradle are built around fetching dependencies from the network at build time. That's simply how those ecosystems work. An input-addressed derivation, being sandboxed and offline by design, can't accommodate that. Point a naive Gradle build at an input-addressed derivation and it fails outright, because the tool has no way to resolve anything it needs.
The need underneath that failure is legitimate, though. Fetching a source tarball, or a dependency artifact, from a known URL is a routine and necessary part of packaging real software. Nix's answer is the fixed-output derivation: let the builder reach the network, but require the derivation to declare, in advance, the exact content hash the output must match. That declaration happens through the outputHash, outputHashAlgo, and outputHashMode attributes.
The mechanics, laid out in the Nix 2.34 reference manual, are straightforward. The builder runs with network access. Once it finishes, Nix checks the actual output bytes against the declared hash. A match means the output is accepted into the store. A mismatch fails the build outright, no partial credit. And if the output already exists in the store or a binary cache, Nix can skip the build step entirely.
A common pattern, following a worked example from Brian McGee, splits a Gradle-based application into two derivations. The first is a FOD that runs Gradle with network access, just to populate an offline Maven repository cache. The second is an ordinary input-addressed derivation that runs Gradle again, this time fully offline, against that cached dependency set. Network access gets contained to exactly the step that needs it.
The Nix 2.28 reference manual states the security logic behind this plainly: whatever the builder does during a FOD build, however it behaves on the network, it can't influence anything downstream in unexpected ways, because everything it passes forward has to flow through an output whose content address is already fixed. One more constraint worth noting: FODs are limited to a single output named out, per the 2.28, 2.29, and 2.32 manuals. The documentation itself calls this an arbitrary restriction, one that could eventually be lifted, but for now it's a hard rule.
The nuance most explanations miss: FODs are not content-addressed at the store level
Here's where a lot of otherwise careful explanations go sideways. A FOD declares a content hash, so it's tempting to call it a content-addressed derivation. That's wrong, and the distinction matters more than it looks.
"Content-addressed," in Nix's own vocabulary, refers specifically to how a store path gets computed. A genuinely content-addressed path is derived from the output bytes themselves. FOD store paths aren't computed that way. They're computed from derivation metadata such as the declared hash, name, and method, not from the output bytes themselves. outputHash isn't an addressing mechanism at all. It's a verification constraint, checked after the fact. The path was locked in before the build ever ran.
The practical consequence is easy to demonstrate and a little counterintuitive. Because a FOD's store path depends on the declared hash and name rather than the output bytes, a URL change alone may not alter the store path if the declared hash and name remain the same, whereas a truly content-addressed system derives the path entirely from the output bytes. A truly content-addressed system wouldn't behave that way. Floating CA derivations, under the ca-derivations experimental feature, are built exactly to avoid this: the path comes from the output itself, so a URL swap that fetches the same bytes produces the same path and no rebuild at all.
Laying out the full matrix helps make the boundaries concrete:
Input-addressed and pure is the default for ordinary derivations: sandboxed, no network, path fixed from inputs before the build starts.
Fixed content-addressed and impure is the FOD: network allowed, output checked against a declared hash, but the path is still fixed from inputs, not from the output.
Floating content-addressed and pure is the experimental ca-derivations path: the store path comes from the output itself, which is what allows maximal deduplication.
Floating content-addressed and impure only turns on when both ca-derivations and impure-derivations are enabled together.
Pure combined with fixed content-addressing isn't supported, and for good reason: the entire point of fixing a store path ahead of time is to permit controlled impurity. Fixing the path without allowing any impurity would serve no purpose at all.
Conflating "verifies content" with "addressed by content" isn't a pedantic distinction. Get it wrong and the reasoning about when rebuilds happen, and what a store path actually proves, goes wrong right along with it.
What each model guarantees, and what it leaves unverified
An input-addressed derivation guarantees that the build process was deterministic relative to its declared inputs: same inputs, same environment, same commands, same output path. What it does not guarantee is that the output bytes are actually what you think they are, particularly if some input further back in the chain was itself fetched impurely at an earlier point.
A fixed-output derivation flips that. It guarantees the content of what was fetched: the bytes match the hash that was declared ahead of time, regardless of whatever the network did or didn't cooperate with during the build. What it does not guarantee is that the hash was trustworthy the moment someone first wrote it into the expression. A hash committed carelessly, or a hash pointing at something already compromised, gets accepted just as readily as a correct one. The guarantee operates at verification time, against a declaration, not against ground truth.
There's a containment property worth sitting with here. Per the Nix 2.28 manual, a FOD builder with network access can't poison anything downstream, because its output is fixed and bounded: whatever chaos the build step gets up to internally stays inside that boundary. Floating CA derivations add a further property on top of this: two derivations that happen to produce identical output share a store path, so a change that's purely cosmetic, a comment in source, a URL pointing at the same bytes, stops propagating rebuilds altogether.
Then there's the deliberate escape hatch: impure derivations, unsandboxed, with no build trace recorded. Nix acknowledges outright that these aren't reproducible. They're useful precisely when re-running a build is cheap and the output is expected to vary anyway, so paying for a false promise of determinism doesn't buy anything.
Choosing among these models is really choosing which claim you're willing to stand behind: a claim about process fidelity, a claim about output identity, or no claim at all beyond convenience.
How the cascade rebuild problem shapes real-world Nix at scale
The rebuild cascade isn't a defect in the input-addressed model. It's the model working exactly as designed. That doesn't make it cheap.
A single-byte change to glibc, even one with zero effect on downstream behavior, invalidates the store path of everything on Linux that references it, directly or indirectly. That triggers rebuilds across enormous swaths of the dependency graph. NixOS copes with this the only way that scales: a large build cluster paired with a substantial binary cache, so individual users substitute a pre-built artifact from cache instead of rebuilding locally every time.
FODs help here too, in a narrower way. By anchoring a fetched input to a declared content hash, they give Nix a stable identifier for the output that is independent of the source URL.
Floating CA derivations go further still, structurally. Nix can recognize that a package built before an OpenSSL patch is functionally unchanged, differing only in which store path variant of OpenSSL it happens to reference, and skip the rebuild entirely. Floating CA derivations are specifically designed to enable this kind of deduplication based on output content. Purely cosmetic changes stop generating rebuild noise across the graph.
The tradeoff for teams choosing between these is fairly clean. Input-addressed is stable, well understood, and needs no experimental flags turned on anywhere. Floating CA requires opting into ca-derivations, brings real complexity with it, and trades that complexity for a meaningfully lower rate of spurious rebuilds. For CI/CD pipelines specifically, this isn't abstract: cache hit rates track directly with how stable the dependency graph stays, and a single upstream dependency that updates frequently can quietly degrade caching across a large number of otherwise unrelated jobs.
What FODs mean for supply chain integrity and dependency provenance
Every fixed-output derivation is, in effect, an explicit and auditable claim: this artifact, fetched from this source, has exactly this content hash. That claim lives in the Nix expression itself, versioned in the repository alongside everything else, not tucked away in some build log nobody reads after the fact.
That's a meaningfully different guarantee than a lockfile recording a version number. A version label can be re-published under the same name with different content behind it. A content hash covers the actual bytes, and if the bytes change, the hash stops matching, full stop.
Because input-addressed builds run inside a sandbox, provenance in Nix isn't a document someone writes after the fact to describe a release. It's a property built into the derivation graph itself, generated as a side effect of how the build actually happened. That's precisely why a small ecosystem of Nix-specific SBOM tooling has grown up around the derivation graph as its natural source of truth. nikstur/bombon generates SBOMs at the .nix expression level, with access to package metadata. tweag/genealogos uses nixtract for dependency introspection and produces a hierarchical representation of the dependency graph. tiiuae/sbomnix generates SBOMs from a flake reference or a store path, with pruning support for build-time versus runtime dependencies, and ships alongside companion tools: nixgraph for visualizing the dependency graph, nixmeta for pulling nixpkgs metadata, and vulnxscan for running vulnerability scans off the generated SBOM. FlakeBOM produces CycloneDX-format SBOMs from any flake, and its companion FlakeAudit checks those SBOMs against custom policy and can fail a CI run on a violation, catching problems before release rather than during a post-hoc audit.
This lines up with where regulation is heading, too. The EU Cyber Resilience Act's Annex I requires a bill of materials in a commonly used, machine-readable format, and requires it to stay current, not just accurate at the moment of one release. A static SBOM generated once at ship time structurally struggles to meet that bar. An SBOM generated from the derivation graph itself, anchored by FOD content hashes for every fetched dependency, reflects what was actually built rather than what a manifest once claimed. SLSA provenance attestations, paired with signing tools like Sigstore and cosign, extend this idea further still, building a cryptographically verifiable chain of custody from original source all the way through to the final artifact.
Choosing the right model: a practical decision framework
Input-addressed, the default, is the right call when every input is already sitting in the store, when the build is fully sandboxed and deterministic from end to end, and when what you actually want is the strongest available guarantee about process fidelity, not about the specific bytes produced.
A fixed-output derivation is the right call when a build step genuinely has to reach the network, fetching a source archive or a dependency artifact from a known location, and no amount of sandboxing discipline is going to change that requirement. In that case, the honest move isn't to pretend the network dependency doesn't exist. It's to contain it: let the fetch happen, and pin the result to a hash that gets checked every time afterward.
Floating content-addressed derivations, still experimental, make sense for teams that have already hit the cascade problem hard enough to feel it in CI wait times and cache misses, and who are willing to take on the complexity of ca-derivations in exchange for a dependency graph that stops propagating rebuilds altogether.
None of these three is a default best answer sitting above the others. Each one is a different guarantee, built for a different kind of build step, and the discipline is in matching the guarantee to the problem actually in front of you, rather than reaching for whichever model happens to be the one most people reach for out of habit.
Sources
- Derivation Outputs and Types of Derivations - Nix 2.28.8 Reference Manual
- Derivation Outputs and Types of Derivations - Nix 2.29.5 Reference Manual
- Derivation Outputs and Types of Derivations - Nix 2.32.9 Reference Manual
- Advanced Attributes - Nix 2.34.9 Reference Manual
- Nix: what are fixed-output derivations and why use them? | Brian McGee
- Glossary - Nix 2.28.8 Reference Manual
- tweag.io
- functor.tokyo

