Derivations as an idea
Derivations as an idea
Goal: Treat a derivation as a pure build plan: relate evaluation → .drv → store outputs; run a tiny build; read logs; explain sandbox and fixed-output derivations (FODs) at a conceptual level.
Deep stdenv phase mastery lives in the Packaging part of this book. This chapter is the idea and a successful build loop.
Why this chapter matters
Without “derivation,” the store is a pile of directories and flakes are just JSON with extra steps. Derivations are the contracts that produce store paths.
Ops motivation: cache misses, hash mismatches, sandbox failures, and “why did CI compile?” all sit on this abstraction. You cannot operate Nix without naming the plan.
Theory 1 — Derivation = build recipe with a promised output path
A derivation answers:
- What inputs (other store paths, sources)?
- What builder runs (usually bash + stdenv)?
- What environment (env vars, sandbox)?
- What output path(s) will hold results?
Informally:
derivation D
inputs: I1, I2, …
builder: B
→ output path P = hash(inputs, builder, args, system, …)
In the common input-addressed model, the hash is about the plan, not the bytes of the result (contrast content-addressed—Internals part).
Vocabulary table
| Term | Meaning |
|---|---|
| Derivation | Build plan object / .drv |
| Output | Realized store directory |
| Builder | Program that performs the build |
| Fixed-output derivation | Special case: hash of content declared |
| Dependent derivation | Normal build; hash of inputs |
Theory 2 — Eval vs realize (operational)
| Phase | Artifact | Command vibes |
|---|---|---|
| Evaluate | Nix values + derivation objects | nix eval, instantiate |
| Low-level plan | .drv file in the store |
nix derivation show |
| Realize | Output path directories | nix build |
| Run | Binaries inside outputs | nix run, ./result/bin/… |
.drv is not the package
/nix/store/xxxx-hello-2.12.drv ← plan
/nix/store/yyyy-hello-2.12 ← output (realized)
You ship and cache outputs; .drv explains how they were supposed to be built.
Pipeline sketch
flake/expression
│ eval
▼
derivation graph (.drv files)
│ realize (build or substitute)
▼
output paths in /nix/store
│ references
▼
closure
Theory 3 — mkDerivation mental model (lite)
Most software uses stdenv.mkDerivation:
stdenv.mkDerivation {
pname = "mytool";
version = "0.1.0";
src = ./src;
buildInputs = [ /* runtime/build deps */ ];
# phases: unpack, patch, configure, build, install, fixup, …
}For learning in this chapter, prefer trivial builders that avoid compilers:
pkgs.writeText "hello.txt" "hello from nix\n"
pkgs.writeShellScriptBin "greet" ''
echo "hello from store script"
''These still produce real store paths and teach the loop without a C toolchain fight.
Other trivial helpers (awareness)
| Helper | Result |
|---|---|
writeText |
File in store |
writeTextFile |
More options |
writeShellScript |
Script path |
writeShellScriptBin |
$out/bin/name |
runCommand |
Tiny custom builder |
Theory 4 — Sandbox: why builds are fenced
The build sandbox aims to make builds pure:
| Restriction (typical Linux sandbox) | Why |
|---|---|
| Limited filesystem view | No sneaking /usr/lib deps |
| No network (usually) | Downloads must be fixed-output derivations |
| Clean env | Fewer “works on my machine” env vars |
| Only declared inputs visible | Graph stays honest |
Fixed-output derivations (FODs)
Fetchers (fetchurl, fetchFromGitHub) are special: they may use network but must declare a hash of the result. Wrong hash → fail. That is how purity reconciles with downloading sources.
fetchurl {
url = "https://example.com/foo.tar.gz";
sha256 = "…"; # wrong → error
}| Property | Normal derivation | FOD |
|---|---|---|
| Network | Usually blocked | Allowed |
| Output hash | From inputs (plan) | Declared (content) |
| Failure mode | Build error | Hash mismatch / fetch fail |
Why FODs matter to operators
- Pinning sources makes rebuilds reproducible
- Hash mismatch is a security and integrity signal
- “Works with wrong hash” is not a feature
Theory 5 — Substituters vs local build
Realization of path P:
1. Is P already in the local store? → done
2. Can a trusted substituter provide P? → download
3. Else build from .drv locally
Implications
- First build can be “download a lot from cache.nixos.org”
- Changing one input changes the hash → cache miss → compile
- Trust is about signatures, not “HTTP has a file”
| Observation | Interpretation |
|---|---|
Instant hello |
Substituted |
| Long compile after one-line change | Input hash changed |
| Hash mismatch on fetch | FOD integrity failure |
Theory 6 — Outputs and multiple outputs (preview)
A derivation can have several outputs (out, dev, doc, …):
P-out, P-dev, P-man
Closure size discipline later depends on not pulling dev into runtime. In this chapter: know out is the default.
nix derivation show nixpkgs#hello | head -c 1500
# look for output names in the JSON-ish structureTheory 7 — What you inspect after a build
| Tool | Use |
|---|---|
nix build … --print-out-paths |
Output path |
ls result |
Symlink to output |
nix log <path or installable> |
Build log |
nix derivation show |
JSON-ish plan |
nix path-info -rS |
Closure size |
Reading a log like an operator
- Was it a substitute or a local build?
- Which phase failed (if stdenv)?
- Missing dependency vs sandbox network vs hash?
Theory 8 — Impure vs pure evaluation (awareness)
| Mode | Risk |
|---|---|
| Pure flake eval | Preferred; limited filesystem |
--impure |
Can see ambient state; less reproducible |
builtins.currentSystem |
Convenient; pin system when sharing |
Prefer pure flakes in project work (next chapters).
Worked examples bank
Example A — Build hello from nixpkgs
nix build nixpkgs#hello --print-out-paths
./result/bin/helloExample B — Trivial package via flake
See Lab 1. Prefer flakes over one-off impure exprs for cleanliness.
Example C — Read derivation metadata
OUT=$(nix build nixpkgs#hello --print-out-paths --no-link)
nix derivation show nixpkgs#hello | head -c 2000
nix log "$OUT" 2>/dev/null || nix log nixpkgs#hello | tailExample D — Sandbox failure story (conceptual)
Builder does: curl http://example.com/dep.so
→ network blocked (non-FOD)
→ build fails
→ declare dep as input or use fetchurl with hash
Example E — runCommand sketch
pkgs.runCommand "note" { } ''
mkdir -p $out
echo "built at eval-defined plan" > $out/note.txt
''Example F — Wrong FOD hash
Expect failure when sha256 does not match downloaded bytes—note the error class for packaging later.
Lab 1 — Workspace + flake for trivial builds
mkdir -p ~/lab/nixos-book/concepts/derivations
cd ~/lab/nixos-book/concepts/derivations
git initCreate flake.nix:
{
description = "trivial derivations lab";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }:
let
system = "x86_64-linux"; # change if needed: aarch64-linux, aarch64-darwin, …
pkgs = import nixpkgs { inherit system; };
in {
packages.${system} = {
greet = pkgs.writeShellScriptBin "greet" ''
echo "hello from a derivation"
'';
note = pkgs.writeText "note.txt" ''
Derivations plan store paths.
'';
default = self.packages.${system}.greet;
};
};
}If your system is not x86_64-linux, edit system. Discover with:
nix eval --impure --expr 'builtins.currentSystem'nix flake lock
nix build .#greet
./result/bin/greet
nix build .#note
cat resultCommit flake.lock when generated.
Lab 2 — Inspect plan and log
nix build .#greet --print-out-paths
nix derivation show .#greet | head -n 80
nix log .#greet | tail -n 40Journal:
- Output path hash/name
- Whether the “build” was essentially a write to the store
- One field you saw in
derivation showthat looks like an input
Lab 3 — Compare to nixpkgs hello closure
H=$(nix build nixpkgs#hello --print-out-paths --no-link)
G=$(nix build .#greet --print-out-paths --no-link)
echo "hello requisites: $(nix-store -q --requisites "$H" | wc -l)"
echo "greet requisites: $(nix-store -q --requisites "$G" | wc -l)"
nix path-info -rS "$H" | tail -3
nix path-info -rS "$G" | tail -3Explain size difference in one sentence (runtime interpreter/libs vs tiny script).
Lab 4 — Break a FOD on purpose (optional)
nix build --impure --expr 'let pkgs = import (builtins.getFlake "nixpkgs") { system = builtins.currentSystem; }; in pkgs.fetchurl { url = "https://example.com"; sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; }' 2>&1 | tailYou want a hash mismatch or fetch failure—not a success. Note the error class for later packaging chapters.
Lab 5 — Teach-back
Define without notes:
- Derivation
.drvvs output path
- Why sandbox blocks network by default
- Substituter vs local build
- Why
writeShellScriptBinis still a “real” package
Exercises
- Add a second script package to the flake; make it
default.
- Use
--no-linkand explain GC-root impact.
- Diff
derivation showforgreetvshello(structural notes).
- Force a rebuild (when applicable) and compare to substitute path.
- Write
runCommandoutput containing your username string (careful: purity).
- Map each stdenv phase name to one sentence (even if you skip implementing).
- Document sandbox vs FOD in a two-column table from memory.
- Measure closure size before/after adding
pkgs.gitto a wrapper (optional).
- Explain multi-output
devrisk for runtime closures.
- Commit flake + lock; clone to a second directory and rebuild offline if possible.
- Trigger
systemmismatch by asking for the wrong triple; fix it.
- List three realize failures and which layer they belong to.
Common pitfalls
| Symptom | Theory |
|---|---|
Wrong system in flake |
Outputs empty / wrong attr path |
result pins GC root |
rm result when done experimenting |
| Edited files in store | Immutable—change expression, rebuild |
| “Build” always compiles | Substituters often download |
| Impure path surprises | Unqualified imports / currentSystem |
| Darwin vs Linux scripts | shebangs and system must match host |
| FOD wrong hash ignored | Never; must match |
Confusing .drv with runnable output |
Plan vs product |
Checkpoint
- Built a local trivial package via flake output
- Ran the resulting binary/script
- Inspected derivation show + log
- Compared closure size to
hello
- Explained sandbox + FOD at awareness level
flake.lockpresent
- Eval → drv → output story told in own words
Further depth (this book)
| Topic | Chapter / part |
|---|---|
| Modern CLI around builds | Modern Nix CLI |
| Project shells | Flakes and project devShells |
| stdenv phases, FODs deep | Part Packaging |
| CA derivations | Part Internals |
| Remote builders / caches | Packaging + Ops parts |