Derivation

A derivation is the fundamental unit of build in Nix. It describes how to build something — a package, a script, or even an entire system — in a purely functional way.

Think of it as the recipe that Nix uses to produce an output in the Nix store.

What is a Derivation?

A derivation is a function call in the Nix language that returns a .drv file inside the Nix store. This .drv file specifies:

  • The inputs (sources, dependencies, environment variables).
  • The builder (program/script used to build).
  • The outputs (files/directories produced).

The .drv file is stored under a hash-based path, e.g.:

/nix/store/<hash>-hello-2.12.1.drv

When realized (built), it produces an output path, like:

/nix/store/<hash>-hello-2.12.1

Output Structure

The output of a derivation is a directory in the Nix store, typically following an FHS-like structure (bin/, lib/, share/, etc).

For example, the derivation for GNU Hello will produce:

/nix/store/<hash>-hello-2.12.1/
├── bin/
│   └── hello
├── share/
│   └── info/…
└── lib/

By default, a Nix derivation has one output, which is available as the variable $out inside the derivation’s build script. But sometimes, you want multiple outputs. For example:

  • dev for development files (headers, static libraries)
  • bin for executables
  • man for manual pages
  • doc for documentation

You can declare them using outputs:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12";
  src = fetchurl {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.tar.gz";
    sha256 = "...";
  };
 
  outputs = [ "out" "dev" "man" ];
 
  # Only build 'out', the default, by default
  installPhase = ''
    mkdir -p $out/bin
    cp hello $out/bin/
 
    mkdir -p $dev/include
    cp hello.h $dev/include/
 
    mkdir -p $man/share/man/man1
    cp hello.1 $man/share/man/man1/
  '';
}
  • $out → main output (runtime files, e.g., binaries)
  • $dev → development files (headers, .pc files, static libraries)
  • $man → man pages

Each of these outputs is a path in the Nix store.

When using derivations with multiple outputs in Nix, you can access them like this:

buildInputs = [ hello.out ];   # runtime
nativeBuildInputs = [ hello.dev ];  # for headers/libraries at build time

Nixpkgs has some conventions:

OutputPurpose
outMain runtime files (binaries, libraries)
devDevelopment files (headers, static libraries, pkg-config files)
docDocumentation
manMan pages
binExecutables (sometimes separate from libraries in out)
libShared libraries (if splitting library from binaries)

Not all derivations have all outputs; you define what makes sense.

mkDerivation

In practice, packages are defined using helper functions rather than raw derivation. The most simple is stdenv.mkDerivation from the Nixpkgs library, which provides sensible defaults.

Example:

{ stdenv, fetchurl }:
 
stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";
 
  src = fetchurl {
    url = "mirror://gnu/hello/hello-${version}.tar.gz";
    sha256 = "0ssi1…";
  };
 
  meta = {
    description = "GNU Hello";
    license = stdenv.lib.licenses.gpl3;
    maintainers = [ stdenv.lib.maintainers.example ];
  };
}

Bigger Example:

{ stdenv, fetchFromGitHub, lib, pkg-config, zlib }:
 
stdenv.mkDerivation rec {
  pname = "mytool";
  version = "1.0.2";
 
  src = fetchFromGitHub {
    owner = "myuser";
    repo = "mytool";
    rev = "v${version}";
    sha256 = "0abcd1234…";
  };
 
  nativeBuildInputs = [ pkg-config ];
  buildInputs = [ zlib ];
 
  configureFlags = [ "--enable-feature-x" ];
  makeFlags = [ "PREFIX=$out" ];
 
  prePatch = ''
    substituteInPlace source.c \
      --replace "foo" "bar"
  '';
 
  buildPhase = ''
    runHook preBuild
    ./configure ${configureFlags} \
      --prefix=$out
    make ${makeFlags}
    runHook postBuild
  '';
 
  installPhase = ''
    runHook preInstall
    mkdir -p $out/bin
    cp mytool $out/bin/
    runHook postInstall
  '';
 
  # metadata
  meta = with lib; {
    description = "My awesome command-line tool that does X";
    homepage = "https://github.com/myuser/mytool";
    license = licenses.mit;
    maintainers = [ maintainers.myuser ];
    platforms = platforms.linux;
  };
}