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:
devfor development files (headers, static libraries)binfor executablesmanfor manual pagesdocfor 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,.pcfiles, 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 timeNixpkgs has some conventions:
| Output | Purpose |
|---|---|
out | Main runtime files (binaries, libraries) |
dev | Development files (headers, static libraries, pkg-config files) |
doc | Documentation |
man | Man pages |
bin | Executables (sometimes separate from libraries in out) |
lib | Shared 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;
};
}