Derivations as an idea

Updated

July 30, 2026

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.

Note

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:

  1. What inputs (other store paths, sources)?
  2. What builder runs (usually bash + stdenv)?
  3. What environment (env vars, sandbox)?
  4. 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 structure

Theory 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

  1. Was it a substitute or a local build?
  2. Which phase failed (if stdenv)?
  3. 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/hello

Example 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 | tail

Example 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 init

Create 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 result

Commit 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 40

Journal:

  1. Output path hash/name
  2. Whether the “build” was essentially a write to the store
  3. One field you saw in derivation show that 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 -3

Explain 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 | tail

You 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:

  1. Derivation
  2. .drv vs output path
  3. Why sandbox blocks network by default
  4. Substituter vs local build
  5. Why writeShellScriptBin is still a “real” package

Exercises

  1. Add a second script package to the flake; make it default.
  2. Use --no-link and explain GC-root impact.
  3. Diff derivation show for greet vs hello (structural notes).
  4. Force a rebuild (when applicable) and compare to substitute path.
  5. Write runCommand output containing your username string (careful: purity).
  6. Map each stdenv phase name to one sentence (even if you skip implementing).
  7. Document sandbox vs FOD in a two-column table from memory.
  8. Measure closure size before/after adding pkgs.git to a wrapper (optional).
  9. Explain multi-output dev risk for runtime closures.
  10. Commit flake + lock; clone to a second directory and rebuild offline if possible.
  11. Trigger system mismatch by asking for the wrong triple; fix it.
  12. 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.lock present
  • 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