Packaging craft
Packaging craft
This part of the standalone NixOS book is organized by topic (normal book chapters), not by calendar day.
Read chapters in order within the part when learning; skip or skim freely when using the book as a reference.
Baseline: Nix 2.34.x, flakes-first workflows, nixpkgs / NixOS 26.05. Language builders, FOD hash fields, and CLI flags evolve—prefer patterns that match current nixpkgs examples when a helper renames an attribute.
What this part is for
You already run hosts, compose modules, and pin flakes. Packaging craft is how you:
- Turn source into store paths with deliberate
stdenvphases
- Pin bytes with fixed-output derivations (FODs)
- Use language ecosystems without cargo-culting random blogs
- Patch and override without forking nixpkgs forever
- Split outputs, debug sandboxes, navigate nixpkgs layout
- Feed fleets with binary caches and remote builders
- Speak honestly about reproducibility
Without this part, “we use Nix” means only consuming environment.systemPackages. With it, you produce packages, own supply-chain pins, and debug CI the same way you debug a laptop.
Prerequisites
| Skill | Where it came from |
|---|---|
| Derivations, store paths, closures | Concepts part |
Flakes, callPackage, overlays (light) |
Home and flake layout |
| Host consumption of packages | NixOS host part |
| Optional: secrets for cache tokens | Services and security |
You do not need prior C packaging expertise. You do need willingness to read build logs.
Chapter map
| Chapter | File | Focus |
|---|---|---|
| stdenv and build phases | 01-stdenv-phases |
genericBuild, hooks, inputs kinds |
| Fetchers and FODs | 02-fetchers-fod |
Hash workflow, trust, flake inputs vs src |
| Language ecosystems A | 03-language-ecosystems-a |
One deep language builder |
| Language ecosystems B | 04-language-ecosystems-b |
Second ecosystem; pattern transfer |
| Patching and overrides | 05-patching-overrides |
overrideAttrs, overlays, patches |
| Multiple outputs | 06-multiple-outputs |
out/dev/man, closure size |
| Debugging builds | 07-debug-builds |
Logs, keep-failed, breakpointHook |
| Navigating nixpkgs | 08-nixpkgs-layout |
by-name, PR-shaped packages |
| Binary caches | 09-binary-caches |
Substituters, Cachix/Attic/Harmonia, trust |
| Remote builders | 10-remote-builders |
Offload realization; features |
| Reproducibility | 11-reproducibility |
L0–L5 honesty, measurement |
Suggested linear path: 01 → 02 → 03 → 04 → 05 → 06 → 07 → 08, then 09 ↔︎ 10 as a pair, finish with 11.
Mental model — three layers of “a package”
┌─────────────────────────────────────────────┐
│ Meta & policy (license, mainProgram, …) │
├─────────────────────────────────────────────┤
│ Build plan (phases, hooks, language helper)│
├─────────────────────────────────────────────┤
│ Fixed inputs (src FOD, vendor/cargo/npm …) │
└─────────────────────────────────────────────┘
│
▼
/nix/store/…-pname-version
| Layer | Failure class | First tool |
|---|---|---|
| Inputs | Hash mismatch, floating tag | Prefetch / fakeHash |
| Build | Compile, link, install | -L, nix log |
| Meta / consume | Wrong bin name, missing wrap | nix run, path-info |
stdenv is the spine
Almost every language helper is still stdenv.mkDerivation plus setup hooks:
buildGoModule / buildRustPackage / buildPythonApplication / buildNpmPackage
│
▼
stdenv.mkDerivation (+ setup hooks)
│
▼
unpack → patch → configure → build → check → install → fixup
If you cannot name the default phase order, every “mysterious” failure later is slower than it needs to be. The stdenv chapter is non-skippable for practitioners who only ever used nix profile install.
FODs vs normal builds
| Kind | Network in sandbox? | Output path depends on |
|---|---|---|
| Normal | No (default) | Derivation inputs |
| Fixed-output | Yes, limited | Declared output hash |
Package src and language dependency graphs are almost always FODs. Updating a hash is a trust decision, not a clerical annoyance—covered in the fetchers chapter and again under reproducibility.
Caches vs builders (preview)
These two chapters are confused constantly:
[dev] --build--> [builder] --push--> [cache] --substitute--> [fleet]
| Mechanism | Moves | Needs existing path? |
|---|---|---|
| Binary cache | Results (NARs) | Yes |
| Remote builder | Work (realize drv) | No |
Packaging craft produces the paths; ops and CI distribute them. Read both packaging chapters before wiring fleet CI.
Lab workspace conventions
Suggested roots (adjust freely):
~/lab/nixos/packaging/
stdenv/ # shell + C toys
fod/ # fetchurl / GitHub drills
eco-a/ # primary language tool
eco-b/ # second ecosystem
overrides/ # patches + overlays
multi-out/ # lib + app
debug/ # intentional breaks
by-name/ # nixpkgs-shaped layout
cache/ # push/pull notes
builders/ # remote build topology
repro/ # REPRO.md + measurements
Each chapter’s lab can be a separate flake or packages inside one monorepo flake—prefer one flake with packages.* attrs if you already have a lab flake.
# Recurring commands
nix build .#attr -L
nix run .#attr
nix flake check
nix path-info -rS ./result | sort -k2 -n | tail
nix log .#attrHow chapters are structured
Most chapters use the same spine so you can scan as a reference:
- Goal — what “done” looks like
- Why it matters — ops/packaging motivation
- Theory — models, tables, complete Nix snippets
- Worked examples — copyable, 26.05-shaped
- Lab — multi-step exercises
- Pitfalls — symptom → fix tables
- Checkpoint — self-review list
- Commit — suggested message (optional discipline)
Reading paths
Path A — “I need one internal CLI in nixpkgs style”
- stdenv phases
- Fetchers / FODs
- Language ecosystems A (your language)
- Debugging builds
- nixpkgs layout (meta + by-name shape)
- Reproducibility (write claims)
Path B — “CI is compiling the world”
- Binary caches (trust + push)
- Remote builders
- Debugging builds (cache miss vs compile fail)
- Reproducibility (L1/L2 measurements)
Path C — “We forked half of nixpkgs”
- Patching and overrides
- Multiple outputs (if closures bloated)
- nixpkgs layout (upstream-shaped expressions)
- Language ecosystems B (stop reinventing builders)
Glossary (part-local)
| Term | Meaning |
|---|---|
| stdenv | Standard environment: toolchain + genericBuild driver |
| Phase | Named step (buildPhase, installPhase, …) |
| Hook | preInstall / postFixup style glue around phases |
| FOD | Fixed-output derivation; hash pins content |
| SRI hash | sha256-… form preferred in modern nixpkgs |
| vendorHash / cargoHash / npmDepsHash | Language-dep FODs |
| overrideAttrs | Change derivation attributes after the fact |
| Overlay | Function final: prev: { … } reshaping pkgs |
| Multiple outputs | Separate store paths (out, dev, …) |
| Substituter | Binary cache endpoint Nix may pull from |
| Remote builder | Machine that realizes derivations for you |
| L1 / L2 repro | Same path on rebuild / across similar hosts |
Quality bar for “done packaging”
A package is not “done” when it builds once on your laptop. Aim for:
| Criterion | Check |
|---|---|
| Builds from flake lock | nix build .#pkg -L |
| Runnable entrypoint | meta.mainProgram + nix run |
| Hashes intentional | No fakeHash left committed |
| Logs readable | You can name the failing phase |
| Meta honest | license, description, platforms |
| Closure sane | nix path-info -Sh; multi-out if library |
| Claim measured | REPRO note for anything you ship to others |
Relationship to other parts
| Part | Connection |
|---|---|
| Concepts | Derivations and store theory become muscle memory here |
| NixOS host | environment.systemPackages and modules consume packages |
| Home / flakes | Overlays, checks, lock discipline |
| Services / security | Cache tokens and signing keys are secrets |
| Ops and fleet | Deploy tools ship closures you learned to build |
| Internals | Evaluator, CA derivations, store daemon deepen later |
| Projects | Hands-on packaging drills reuse this part’s patterns |
This book does not cross-link a parallel day-calendar series. Everything here is topic-organized library material.
Anti-patterns to avoid while studying
| Anti-pattern | Prefer |
|---|---|
| Disable sandbox to “make it work” | Find undeclared dep or FOD |
Branch name as rev forever |
Tag or full commit SHA |
| Global overlay for a one-off flag | Local overrideAttrs |
Single $out with headers + docs + bins for shared libs |
Multiple outputs |
| “Fully reproducible” in README without measurement | L1/L2 claims with lock + system |
Copying random default.nix without reading phases |
stdenv + official language helper |
Part checkpoint (before you leave packaging)
- Can list default
stdenvphase order from memory
- Shipped at least one package with a real FOD hash workflow
- One language-builder package end-to-end (ecosystem A)
- Second ecosystem or transfer table completed (ecosystem B)
- Patch + overlay composition demonstrated
- Multi-output or closure inspection done deliberately
- Personal debug runbook exists
- Package shaped like nixpkgs (
meta, preferably by-name layout)
- Cache trust model written (even if only cache.nixos.org)
- Know cache vs remote builder in one paragraph
- Honesty note on reproducibility for one attr
Suggested lab commit series
git commit -m "packaging: stdenv greet + addone"
git commit -m "packaging: FOD hash workflow"
git commit -m "packaging: language ecosystem A"
git commit -m "packaging: language ecosystem B"
git commit -m "packaging: patches and overlays"
git commit -m "packaging: multi-output lib + app"
git commit -m "packaging: debug runbook"
git commit -m "packaging: by-name shape + meta"
git commit -m "packaging: cache trust notes"
git commit -m "packaging: remote builder topology"
git commit -m "packaging: REPRO.md measurements"How to use this part as reference
When something breaks in six months:
| Symptom | Jump to |
|---|---|
| Unknown phase failure | Debugging builds; stdenv phases |
hash mismatch |
Fetchers and FODs |
| Language dep network in build | Ecosystems A/B |
| Need a CVE pin | Patching and overrides |
| Runtime closure huge | Multiple outputs |
| Where do I put this for upstream? | nixpkgs layout |
| CI rebuilds everything | Binary caches; remote builders |
| Manager asks “is it reproducible?” | Reproducibility |
Next
Start with stdenv and build phases: name every default phase, package a shell tool and a tiny C tool, and break install on purpose so logs become familiar. Then pin bytes with FODs before you trust any “reproducible” claim.