Packaging craft

Updated

July 30, 2026

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:

  1. Turn source into store paths with deliberate stdenv phases
  2. Pin bytes with fixed-output derivations (FODs)
  3. Use language ecosystems without cargo-culting random blogs
  4. Patch and override without forking nixpkgs forever
  5. Split outputs, debug sandboxes, navigate nixpkgs layout
  6. Feed fleets with binary caches and remote builders
  7. 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 .#attr

How chapters are structured

Most chapters use the same spine so you can scan as a reference:

  1. Goal — what “done” looks like
  2. Why it matters — ops/packaging motivation
  3. Theory — models, tables, complete Nix snippets
  4. Worked examples — copyable, 26.05-shaped
  5. Lab — multi-step exercises
  6. Pitfalls — symptom → fix tables
  7. Checkpoint — self-review list
  8. Commit — suggested message (optional discipline)

Reading paths

Path A — “I need one internal CLI in nixpkgs style”

  1. stdenv phases
  2. Fetchers / FODs
  3. Language ecosystems A (your language)
  4. Debugging builds
  5. nixpkgs layout (meta + by-name shape)
  6. Reproducibility (write claims)

Path B — “CI is compiling the world”

  1. Binary caches (trust + push)
  2. Remote builders
  3. Debugging builds (cache miss vs compile fail)
  4. Reproducibility (L1/L2 measurements)

Path C — “We forked half of nixpkgs”

  1. Patching and overrides
  2. Multiple outputs (if closures bloated)
  3. nixpkgs layout (upstream-shaped expressions)
  4. 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 stdenv phase 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.