Building Software with Nix
Building Software with Nix: Derivations and Overlays
Section titled “Building Software with Nix: Derivations and Overlays”This section focuses on how software actually gets built in Nix.
The goal is not to memorize helpers, but to understand:
- what a derivation is
- how sources enter the system
- how build helpers reduce boilerplate
- how overlays let you modify builds safely
- and where common pitfalls live
1. What a Derivation Really Is
Section titled “1. What a Derivation Really Is”A derivation is a pure description of a build, not the build itself.
Conceptually:
(inputs) + (build instructions) → derivation → build → /nix/store/…In Nix code, a derivation is just an attrset produced by mkDerivation (or helpers built on top of it).
Key points:
- Evaluation creates a derivation (cheap, fast)
- Building executes it (expensive, sandboxed)
- Derivations are immutable and content-addressed
2. stdenv.mkDerivation: The Foundation
Section titled “2. stdenv.mkDerivation: The Foundation”Most build helpers ultimately call:
pkgs.stdenv.mkDerivationA minimal example:
src/hello.c
Section titled “src/hello.c”#include <stdio.h>
int main(int argc, char *argv[]) { const char *name = (argc > 1) ? argv[1] : "world"; printf("hello, %s!\n", name); return 0;}Derivation
Section titled “Derivation”pkgs.stdenv.mkDerivation { pname = "hello-c"; version = "1.0";
src = ./src;
nativeBuildInputs = [ pkgs.gcc ];
buildPhase = '' gcc hello.c -o hello '';
installPhase = '' mkdir -p $out/bin cp hello $out/bin/ '';
meta = { description = "Simple hello world C program"; };}Important attributes you’ll see constantly:
pname,versionsrcbuildInputsnativeBuildInputsphases/installPhasemeta
3. Fetchers: How Source Code Enters Nix
Section titled “3. Fetchers: How Source Code Enters Nix”Fetchers are fixed-output derivations: they download something and verify its hash.
Common fetchers
Section titled “Common fetchers”fetchFromGitHubfetchgitfetchurlfetchzip
Example:
src = pkgs.fetchFromGitHub { owner = "rakyll"; repo = "hey"; rev = "v0.1.4"; hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";};Key rules:
- Hashes are mandatory (reproducibility)
- Hash mismatch errors are part of the workflow
- Fetchers run at build time, not eval time
4. Build Helpers: Reducing Boilerplate
Section titled “4. Build Helpers: Reducing Boilerplate”Most real packages do not use mkDerivation directly.
Instead, nixpkgs provides build helpers that encode conventions.
Common helpers you’ll encounter
Section titled “Common helpers you’ll encounter”buildGoModulebuildPythonPackagebuildRustPackagebuildNpmPackagecmake,meson,autotoolshelpers
Example (Go):
pkgs.buildGoModule { pname = "hey"; version = "1.0";
src = pkgs.fetchFromGitHub { owner = "rakyll"; repo = "hey"; rev = "v0.1.4"; hash = pkgs.lib.fakeHash; };
vendorHash = pkgs.lib.fakeHash;}Helpers:
- define standard phases
- set environment variables
- handle language-specific tooling
- reduce copy/paste
5. Script Builders and Text Builders
Section titled “5. Script Builders and Text Builders”Not everything is “compile a big project”.
Nix excels at small glue tools.
writeShellApplication
Section titled “writeShellApplication”Best for small CLI tools:
pkgs.writeShellApplication { name = "hello"; text = '' echo "hello from nix" '';}Features:
- wraps runtime dependencies automatically
- clean, declarative
- preferred over raw
mkDerivationfor scripts
writeText / writeTextFile
Section titled “writeText / writeTextFile”For generating config or data:
pkgs.writeText "config.txt" '' port=8080''These return store paths, not strings.
6. Runtime vs Build-Time Dependencies
Section titled “6. Runtime vs Build-Time Dependencies”This is a daily source of confusion.
nativeBuildInputs
Section titled “nativeBuildInputs”- tools needed to build
- run on the build machine
buildInputs
Section titled “buildInputs”- libraries or tools needed at runtime
Rule of thumb:
- compilers, code generators →
nativeBuildInputs - shared libraries, interpreters →
buildInputs
7. Wrapping Programs and Runtime Environment
Section titled “7. Wrapping Programs and Runtime Environment”Many programs expect:
PATHPYTHONPATHSSL_CERT_FILE- etc.
Nix does not provide these implicitly.
wrapProgram
Section titled “wrapProgram”wrapProgram $out/bin/mytool \ --prefix PATH : ${pkgs.python3}/binThis creates a wrapper script that:
- sets environment variables
- then execs the real program
This is how Nix makes programs self-contained.
8. Overlays: Modifying the Package Set
Section titled “8. Overlays: Modifying the Package Set”Overlays are functions that transform the package set.
Signature:
self: super: { … }super→ original package setself→ package set with your overlay applied
Example:
self: super: { hello = super.hello.overrideAttrs (_: { pname = "hello-custom"; });}Overlays are used to:
- patch packages
- change versions
- inject custom builds
- experiment without forking nixpkgs
9. override vs overrideAttrs (Recap)
Section titled “9. override vs overrideAttrs (Recap)”override→ change function argumentsoverrideAttrs→ change derivation attributes
Mental model:
(args) → function → derivation ↑ ↑override overrideAttrsIf you remember nothing else, remember that diagram.
10. callPackage and Dependency Injection
Section titled “10. callPackage and Dependency Injection”callPackage automatically fills function arguments from pkgs.
Instead of:
import ./pkg.nix { inherit pkgs; }You write:
pkgs.callPackage ./pkg.nix {}This:
- reduces boilerplate
- makes overrides easier
- is the standard nixpkgs style
11. Common Day-to-Day Pitfalls
Section titled “11. Common Day-to-Day Pitfalls”These come up constantly:
- Using strings where paths are required
- Forgetting runtime dependencies
- Assuming
/usr/bin/envexists - Confusing eval-time vs build-time
- Overriding the wrong layer (args vs attrs)
- Not checking
meta.platforms
None of these are “advanced” mistakes — they’re normal.
12. How nixpkgs Is Structured (Big Picture)
Section titled “12. How nixpkgs Is Structured (Big Picture)”Understanding this helps you read code:
pkgs/top-level/→ package set assemblypkgs/build-support/→ helperspkgs/development/→ language ecosystemspkgs/tools/→ CLI tools
Most “magic” is just layered functions.
Exercises
Section titled “Exercises”Exercise 1: Fetcher Practice
Section titled “Exercise 1: Fetcher Practice”- Use
fetchFromGitHubwith a fake hash - Build once
- Replace the hash from the error
Exercise 2: Helper Selection
Section titled “Exercise 2: Helper Selection”Given three projects:
- a Go CLI
- a Python library
- a shell script
Decide:
- which build helper to use
- or whether to avoid
mkDerivationentirely
Explain why.
Exercise 3: Overlay Refactor
Section titled “Exercise 3: Overlay Refactor”- Take an existing package
- Modify one argument via
override - Modify one derivation attribute via
overrideAttrs - Verify which changes require rebuilding dependents
Mental Models to Keep
Section titled “Mental Models to Keep”- A derivation is a description, not an action
- Fetchers define trust boundaries
- Helpers encode conventions, not magic
- Overlays are transformations, not patches
- Evaluation is cheap; builds are expensive
Closing Thought
Section titled “Closing Thought”If flakes define what your project is, derivations define how your software exists.
Once you’re comfortable here, you can build almost anything with Nix.