Pills 6 Our First Derivation name system builder .drv instantiate realise — the drv that is not a build
Evaluating a derivation writes a .drv. It does not build. Instantiate names the plan. Realise runs it.
<!-- hal:authoritative:yaml -->
Evaluating a derivation writes a .drv. It does not build. Instantiate names the plan. Realise runs it.
§I — Frame
Asr session 04. Fourth live fire of the Nix language track. Week 2 fire 1. The page is Nix Pills Our First Derivation, Pill 6. Luca Bruno wrote the series. License CC BY-SA 4.0. Ground from the on-disk EPUB at 09-Tomes/Polyglot-Dev/Backend-Stack/Nix/nix-pills.epub, chapter OEBPS/06-our-first-derivation.html. The live URL is canonical if the EPUB drifts: https://nixos.org/guides/nix-pills/06-our-first-derivation.html.
Session 01 read nix.dev Nix language basics. Coin was the set that is not a JSON object. Session 02 was Pill 4. Coin was the slash that is not division. Session 03 was Pill 5 functions and imports. Coin was the scope that does not travel. Those folders stay closed. This session is Pills 6. Not install. Not Pills 1 through 3. Not a working package. Working waits for Pills 7 and session 05. Dolstra 2006 stays on the shelf. NAR and store hashing as a thesis topic are not this lesson. The Pill notes out-path hashing briefly. Blandy is a Rust tome. Empty-by-scope for Nix. No flakes. No Python wrapping.
The Pill says: the derivation builtin takes a set. Required keys are name, system, and builder. Evaluating that call creates a .drv file. It does not build. The .drv is the build specification. The out path is known before any build runs. Realise comes later, with :b in the repl or nix-store -r on the .drv.
Coin the name now: the drv that is not a build.
Done-criteria from the syllabus: you can name name / system / builder, read a .drv, and say instantiate versus realise.
Launch nix repl when you have Nix. Type expressions. Exit with :q. Help with :?. The expressions below match the Pill. Hashes may match the Pill examples. That is fine. Attribute them to the Pill. If Nix is not on the machine today, read the results here. Do not install Nix in this session. The Pill reminds how to enter an existing profile with source ~/.nix-profile/etc/profile.d/nix.sh. That is context only. Do not teach install.
§II — The derivation builtin
derivation is built-in. From the language view, a derivation is a set with some attributes. You can bind it to a name. You can pass it around. The first argument is a set. Three attributes are required:
name: the derivation name. In the store the format ishash-name. That is the name part.system: the system on which the derivation can be built. Example:x86_64-linux.builder: the binary program that builds the derivation.
Ask the machine what Nix calls the current system:
builtins.currentSystem
Result (on the Pill's machine): "x86_64-linux". Your machine may print a different string. Hold the attribute name. The value is local.
Fake the system on purpose. The Pill does this first so you see evaluation without a real build:
# repl: d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; }
d
Result: «derivation /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv».
Did it build? No. It created the .drv file. nix repl does not build derivations unless you tell it to. Evaluating derivation { ... } wrote the plan. It did not run the builder. That is the drv that is not a build.
§III — Digression: what a .drv is
The .drv is the specification of how to build, without the Nix language surface. Analogies from the Pill, mapped to C:
.nixfiles are like.cfiles..drvfiles are intermediate like.ofiles. The.drvdescribes how to build. It is the bare minimum information.- Out paths are then the product of the build.
Both drv paths and out paths live in the nix store.
Pretty-print the .drv. Prefer nix derivation show. If your Nix is older, use nix show-derivation:
nix derivation show /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv
You get a JSON object keyed by the .drv path. Inside: outputs (default one named out, with a predicted store path), inputSrcs, inputDrvs, platform, builder, args, and env. The out path in the Pill example is /nix/store/40s0qmrfb45vlh6610rk29ym318dswdr-myname. That path does not exist yet. You never told Nix to build. You know beforehand where the build output will be.
Why predict the path? If Nix built every derivation the moment you named it in an expression, evaluating a large graph (say Firefox and its closure) would stall on builds. Nix keeps evaluating. It writes .drv files. Out paths are known. The directories stay empty until realise.
Important from the Pill: the hash of the out path is based on the input derivations (in the current version of Nix), not on the contents of the build product. Content-addressable derivations exist later for cases like tarballs. Today, hold the input-hash rule. Do not open Dolstra.
Summary of the .drv contents the Pill names:
- Output paths (default one called
out). - Input derivations (empty when you refer to none).
- System and builder executable.
- Environment variables passed to the builder.
Important note from the Pill: the environment variables passed to the builder are those you see in the .drv, plus some Nix-related configuration (cores, temp dir, and similar). The builder does not inherit variables from your running shell. Shell inheritance would make builds non-deterministic. The builder sees the plan, not your interactive environment.
§IV — Realise attempts and the fake builder
Ask the repl to build:
# repl: d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; }
:b d
:b is a repl command that builds a derivation. See :? for other commands. Output names the .drv, names the out path it is trying to produce, then errors: a mysystem is required, but the machine is (for example) x86_64-linux.
Outside the repl, realise the same .drv with:
nix-store -r /nix/store/z3hhlxbckx4g3n9sw91nnvlkjvyw754p-myname.drv
Same result as :b.
Fix system to the real one:
# repl: d = derivation { name = "myname"; builder = "mybuilder"; system = builtins.currentSystem; }
:b d
Next error: invalid file name 'mybuilder'. Progress. The system matches. The builder binary still does not exist. Stop there for a beat. Evaluation wrote a plan. Realise tried to run it. The plan was wrong about the builder path. The separation still held.
§V — What is in a derivation set
Inspect the return value. It is a plain set with a convention:
# repl: d = derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; }
builtins.isAttrs d
Result: true.
builtins.attrNames d
Result: [ "all" "builder" "drvAttrs" "drvPath" "name" "out" "outPath" "outputName" "system" "type" ].
d.drvAttrs is the input you gave: { builder = "mybuilder"; name = "myname"; system = "mysystem"; }. The fields d.name, d.system, and d.builder match that input.
(d == d.out)
Result: true. With a single output, out is the derivation itself. d.all is a singleton. Multiple outputs come later.
d.drvPath is the .drv store path. d.outPath is the predicted build path. d.type is "derivation". That string is a convention. You can invent a set with type = "derivation" and the repl will print «derivation ???» with no other information. The magic is thin. The fields carry the meaning.
When you write packages later, you care about outputs. The other metadata exists so Nix can form the drv path and the out path.
§VI — Referring to other derivations
Dependencies are other store paths. The convenient handle is outPath. Nix coerces a derivation set to a string when outPath is present:
d.outPath
builtins.toString d
Both yield the out path string. A bare set without outPath cannot coerce:
builtins.toString { outPath = "foo"; }
Result: "foo".
builtins.toString { a = "b"; }
fails: cannot coerce a set to a string.
Load nixpkgs in the repl only as the Pill does, to get coreutils as a derivation value. Do not start a packaging tour:
:l <nixpkgs>
coreutils
builtins.toString coreutils
coreutils prints as a derivation. String coercion yields its out path. Interpolation uses the same rule:
"${d}"
"${coreutils}"
"${coreutils}/bin/true"
The last form is a concrete builder path: the true binary inside the coreutils out path. That is how one derivation refers to files from another.
§VII — An almost-working derivation
Replace the fake builder with true, which exits 0:
:l <nixpkgs>
# repl: d = derivation { name = "myname"; builder = "${coreutils}/bin/true"; system = builtins.currentSystem; }
:b d
The builder runs. Then the build fails: the builder failed to produce the output path. true exited successfully. It created nothing at $out. Every change to the derivation yields a new hash. Obvious, and easy to forget.
Inspect the new .drv with nix derivation show. Now inputDrvs lists the coreutils .drv. Before building yours, Nix would build that input. Coreutils is already in the store, so no rebuild is needed. The builder field is the absolute path to bin/true under the coreutils out path. Referring to another derivation wrote an input edge. Evaluating still did not realise your package. Realise ran the builder and then noticed $out was missing.
§VIII — Instantiate versus realise
Nix does not build during evaluation. That is why the repl needs :b, and why the shell needs nix-store -r.
Two times, from the Pill:
- Instantiate / evaluation. Parse and interpret the Nix expression. Return a derivation set. Create
.drvfiles. Know out paths beforehand. Tool:nix-instantiate. - Realise / build. Take the
.drv, build its inputs first, then build the product into the out path. Tool:nix-store -r.
Think compile then link. First compile sources to objects. Then link. In Nix, first compile the expression to .drv files. Then build each .drv into its out path.
Say it aloud: instantiate writes the plan. Realise runs the plan. Evaluating derivation { ... } is on the instantiate side. :b and nix-store -r are on the realise side. Mixing those words is the common error this session exists to cut.
§IX — Five proofs
Proof 1. Required attrs.
derivation { name = "myname"; builder = "mybuilder"; system = "mysystem"; } evaluates to a derivation value. Name the three required keys without looking them up. If you needed a fourth required key today, walk §II again.
Proof 2. Evaluation is not build.
After the call above, a .drv exists. The out path is empty. :b was never run. If evaluation had built, you would have waited on every named package. Name the drv that is not a build.
Proof 3. Read the .drv.
nix derivation show (or nix show-derivation) prints outputs, inputDrvs, platform, builder, and env. The out path is predicted. Builder env is the .drv env plus Nix config, not your shell. If you expected shell $PATH inside the builder, walk §III again.
Proof 4. Coercion and inputs.
builtins.toString d is d.outPath. "${coreutils}/bin/true" is a builder path. The almost-working derivation adds coreutils to inputDrvs, runs true, and fails because $out was never created. If exit 0 alone counted as a successful package, walk §VII again.
Proof 5. Instantiate versus realise.
nix-instantiate evaluates to .drv. nix-store -r realises. :b in the repl is realise. If those were the same verb, walk §VIII again.
Closing
Pill 6 is the first derivation, and it is intentionally not a working package. You named name, system, and builder. You watched evaluation write a .drv without building. You read the .drv and saw a predicted out path. You failed realise on a fake system, then on a missing builder, then on true with no $out. You inspected the derivation attrset. You coerced through outPath. You watched inputDrvs appear when the builder came from coreutils. You separated instantiate from realise.
Name it when a .drv exists and the out path is still empty: the drv that is not a build. Instantiation is the plan. Realisation is the product. This Pill stops before a builder that writes $out.
Session 05 is Pills 7: working derivation. Do not start it in this folder. Do not write it today. The Pill's own close is the rule: this post is about "our first derivation," and it never claimed the derivation was a working one.
Examine well. Required attrs are the first proof. Evaluation-without-build is the second. Reading the .drv is the third. Coercion and the almost-working builder are the fourth. Instantiate versus realise is the fifth. The door is the empty out path next to a finished .drv.
Related
- Syllabus: Asr Nix syllabus, session 04
- Prior: Asr session 03 · the scope that does not travel
- Prior: Asr session 02 · the slash that is not division
- Prior: Asr session 01 · the set that is not a JSON object
- Grounding tome: Nix Pills EPUB (Pill 6)
- Tome hub: Nix language tomes (CC BY-SA 4.0 note)
- Live URL: https://nixos.org/guides/nix-pills/06-our-first-derivation.html
- Next fire: session 05, Pills 7 working derivation (unwritten)
🛡️ ⚖️ 📜 Leo.Syri — Praetor Consulate, Imperium Luminaura Filed 2026-09-03 · Asr · session 04 · Nix Pills 6 · the drv that is not a build