← Back to Course Index

Lesson 4: Understanding Derivations

Peek under the hood—how Nix builds packages

Learning Objectives

What is a Derivation?

A derivation is Nix's way of describing how to build something. It's a recipe that specifies:

The key insight: A derivation is an expression that evaluates to a description of how to build something—not the built thing itself. Nix evaluates the derivation to get a recipe, then executes that recipe to get the actual artifact.

The Derivation Flow

Nix Expression (derivation) ↓ [Nix evaluates it] ↓ Build Recipe + Content Hash ↓ [Nix builds] ↓ Package in /nix/store/{hash}-{name}

A Simple Derivation: Hello World

Here's a minimal derivation that creates a simple script:

# The simplest possible derivation let pkgs = import <nixpkgs> {}; in pkgs.stdenv.mkDerivation { name = "my-hello"; unpackPhase = "true"; # Skip unpacking buildPhase = '' cat > hello << 'EOF' #!/bin/bash echo "Hello from Nix!" EOF ''; installPhase = '' mkdir -p $out/bin cp hello $out/bin/hello chmod +x $out/bin/hello ''; }
$out: A special variable that points to where your package's output should go in the Nix store. Everything you want to keep must go here.

Understanding mkDerivation

stdenv.mkDerivation is a function that takes an attribute set describing your build. Here are the key attributes:

Basic Attributes

{ name = "hello"; # Package name version = "1.0"; # Version (optional but good practice) src = ./src; # Source code location buildInputs = [ pkgs.gcc ]; # Tools needed to build propagatedBuildInputs = []; # Dependencies for packages that use this }

Build Phases

Derivations follow a standard build process with phases you can customize:

unpackPhase # Extract source (from src) patchPhase # Apply patches if needed configurePhase # Run ./configure buildPhase # Run make installPhase # Install to $out fixupPhase # Final cleanup

The Nix Store

Everything Nix builds goes into /nix/store, organized by content hash:

/nix/store/ ├── 1a2b3c4d5e6f-hello-1.0/ │ ├── bin/hello │ └── share/doc/hello.md ├── 9x8y7z6w5v4u-gcc-11.2.0/ │ ├── bin/gcc │ └── ... └── ...
Content hashing: The hash is derived from the derivation's inputs and build process. Same inputs = same hash. Different inputs = different hash. This enables deduplication and reproducibility.

Derivations Depend on Derivations

Building is really a graph of dependencies:

my-app derivation ├─ depends on gcc derivation ├─ depends on python derivation └─ depends on source code

When you build my-app, Nix automatically:

  1. Builds gcc (if not already built)
  2. Builds python (if not already built)
  3. Uses those outputs as inputs to build your app

A More Realistic Example

Here's a derivation for a simple Python script:

let pkgs = import <nixpkgs> {}; in pkgs.python3.pkgs.buildPythonApplication { pname = "my-tool"; version = "1.0"; src = ./src; propagatedBuildInputs = with pkgs.python3.pkgs; [ requests click ]; meta = { description = "My Python tool"; license = pkgs.lib.licenses.mit; }; }

Important Concepts

Hermetic Builds

Derivations are hermetic: they can only access explicitly-declared inputs. They can't reach out to the internet or access system libraries unless you tell them to. This isolation ensures reproducibility.

Build Inputs vs Runtime Dependencies

buildInputs = [ pkgs.gcc ]; # Needed at build time propagatedBuildInputs = [ pkgs.openssl ]; # Needed at runtime

Try It Yourself

Explore an Existing Derivation

Search nixpkgs for a package and see its derivation:
nix eval 'import <nixpkgs> {}.hello.drvPath'

This shows the path to the derivation file

Show what a derivation looks like:
nix show-derivation 'import <nixpkgs> {}.hello'

You'll see the build recipe as JSON

Build a simple package:
nix build 'import <nixpkgs> {}.cowsay' -o result

This builds cowsay and creates a symlink called result in your current directory

Run what you built:
./result/bin/cowsay "Hello from Nix!"

Key Takeaways