Derivations are recipes to build and distribute apps of any ecosystem in Nix. They are similar to Docker files but better, because the Nix programming language allows custom functions like mkDerivation and mkShell to hide complex details.

In this tutorial we get an overview over:

  1. Raw derivations to see why we need abstractions and helper functions.
  2. The basic helper function mkDerivation to package apps for a Nix.
  3. The helper function writeShellScriptBin to quickly package shell scripts as apps.
  4. The helper function mkShell to declare a devolopment environments only.
  5. Ecosystem specific helper functions.

A Raw Derivation

A Nix derivation is a collection of the source code, dependencies, tools and everything else required to build an app or a package. It’s so generic that we can build almost any artifact from any ecosystem and distribute it via Nix. But with great power comes great amount of configuration. For example, the derivation for a Hello-World app already contains the following data:

$ nix show-derivation nixpkgs#hello
{
  "/nix/store/qp724w90516n4bk5r9gfb37vzmqdh3z7-hello-2.10.drv": {
    "inputSrcs": [
      "/nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh"
    ],
    "inputDrvs": {
      "/nix/store/d9d47vmkcpvz55l5vx4f0s4vry965ahj-stdenv-linux.drv": [
        "out"
      ],
      "/nix/store/l6jacnrdv1qkyaj6jvkrvczz9fkxf51b-bash-4.4-p23.drv": [
        "out"
      ],
      "/nix/store/lwmi9xcp2lzrap72ig56a5xi0qd2i5b3-hello-2.10.tar.gz.drv": [
        "out"
      ]
    },
    ..
    "platform": "x86_64-linux",
    "builder": "/nix/store/jdi2v7ir1sr6vp7pc5x0nhb6lpcmg6xg-bash-4.4-p23/bin/bash",
    ..
    "env": {
      "pname": "hello",
      "version": "2.10"
      "src": "/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz",
      "out": "/nix/store/0pisd259nldh8yfjvw663mspm60cn5v8-hello-2.10",
      "buildInputs": "",
      "nativeBuildInputs": "",
      "propagatedBuildInputs": "",
      "propagatedNativeBuildInputs": "",
      ..
    }
  }
}
Element Note
$ Interactive shell prompt symbol allowing us to type terminal commands.
nix Standard terminal app from the Nix package manager to interact with Nix packages. It has several subcommands. ➡ Subcommands
show-derivation Subcommand to print the raw derivation of the given Nix package as Json. ➡ Docs
nixpkgs#hello Select the Nix package “hello” from the collections of Nix packages. ➡ Package

We see references to other tools like the Bash shell (bash-4.4) and Make (stdenv-linux), the source code (hello-2.10.tar.gz), some “default build script” (default-builder.sh) and other meta information.

Writing all that information by hand would be annoying, so no one really does it. Instead we always see helper functions being used like the ones in the following sections.

mkDerivation

The most common helper function to write Nix derivations is mkDerivation. With it everything is optional except the source code, and the package name and version of what we are bundling up:

# Example hello.nix file
let
  pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
  name = "hello-2.10";

  src = pkgs.fetchurl {
    url = "mirror://gnu/hello/${pname}-${version}.tar.gz";
    sha256 = "0ssi1wpaf7plaswqqjwigppsg5fyh99vdlb9kzl7c9lng89ndq1i";
  };
  ..
}
let .. in ..
Define custom variables in the let-part to use in the in-part for more readable code ➡ Let explanation.
pkgs
Custom variable for the huge collection of all available Nix packages (more than 80000) ➡ Pkgs structure.
import <nixpkgs> {}
Load all available Nix packages ➡ Import explanation.
stdenv
A collection of Nix helper functions.
mkDerivation
A Nix helper function ➡ Nix function syntax.
fetchUrl
A Nix helper function to download source code from the internet.

This helper function is configured to build a package with the build tool Make and the C compiler GCC by default (see builder guide and stdenv guide ). However, we can easily override those settings:

# conceptual "javaproject.nix" file
mkDerivation {
  name = "myJavaProject-1.0.0";
  src = ./src; 
  # override required dependencies
  buildInputs = [ maven jdk ];
  # override build commands
  buildPhase = "mvn clean build";
  installPhase = "mvn install";
}

A real Nix Java package file is actually slightly more complicated, but conceptually it’s just that ➡Java Nix Guide.
More: The Nix language basics
See: mkDerivation in-depth explanation
See: mkDerivation source code
See: Full “hello” Nix package code

info

Having a Nix derivation in a file like javaproject.nix allows us two things: First, to build our app with Nix

$ nix-build javaproject.nix

which places our compiled app under a new ./result folder. Second, to (theoretically) distribute our app via Nix to other users.

Interestingly however, we can also create a development environment with that file:

$ nix-shell javaproject.nix

places us into a shell environment where the required dependencies from buildInputs are available:

[nix-shell:~]$ mvn --version
3.6.3

More: How to use nix-shell
More: Documentation nix-build

writeShellScriptBin

If we have a shell script that we want to package and distribute like an app, we can use a simpler variant of mkDerivation:

# Example "shellscript.nix" file
let
  pkgs = import <nixpkgs> {};
in
pkgs.writeShellScriptBin "greeter" ''
  # Some bash script
  echo Hi
''
let .. in ..
Define variables in the let-part to use in the in-part. Use it for more readable code. ➡ Let explanation.
pkgs
Variable for the collection of all available Nix packages (more than 80000). ➡ Pkgs structure.
import <nixpkgs> {}
Load all available Nix packages ➡ Import explanation.
writeShellScriptBin
A Nix helper function that takes two arguments, a name string and a script string ➡ Nix function syntax.
"greeter"
Our custom executable name for the following shell script.
'' .. ''
Double single quotes are for multiline strings in Nix. Here we use it to embedd a Bash script. ➡ Nix strings.
echo ..
Terminal app that prints the following text to the terminal console.

writeShellScriptBin doesn’t need anything but an executable name and the script text.

$ nix-shell shellscript.nix

places us into a shell environment where our greeter “executable” is available:

[nix-shell:~]$ greeter
Hi

More: writeShellScriptBin examples
See: writeShellScriptBin source code

mkShell

Sometimes we just want to create a development environment and don’t want to build or distribute our app with Nix at all. In that case we quite often see the following simpler variant of mkDerivation:

# Example "java-env.nix" file
let
  pkgs = import <nixpkgs> {};
in
pkgs.mkShell {
  # "override" required dependencies
  buildInputs = [ 
    pkgs.maven 
    pkgs.jdk 
  ];
}
let .. in ..
Custom variables in the let-part to use in the in-part for more readable code. Without “let” we would write (import <nixpkgs> {}).mkShell { ... ➡ Let explanation.
pkgs
Custom variable for the huge collection of all available Nix packages (more than 80000). Think of it as a large Json object { maven:.., jdk:.., git:.. } of packages.
import <nixpkgs> {}
Load all available Nix packages ➡ Import explanation.
stdenv
A collection of Nix helper functions.
mkDerivation
A Nix helper function ➡ Nix function syntax.
fetchUrl
A Nix helper function to download source code from the internet.
buildInputs
List of Nix packages that you want available in the shell environment.
[pkgs.maven pkgs.jdk]
A list in the Nix language. Nix doesn’t use commas in between!

mkShell doesn’t need a package name, a version or the source code; just the list of apps and tools we want to have in our development environment.

$ nix-shell java-env.nix

places us into a shell environment where the apps from buildInputs are available:

[nix-shell:~]$ mvn --version
3.6.3

More: Development environments with nix-shell
More: Nix language basics

info

mkShell is a simplifying wrapper around mkDerivation ➡ mkShell source code and an example of why it’s great to have a configuration programming language like Nix: we can have abstractions, customized to our use case, built on top of other abstractions. *cough* not possible with Docker, Ansible etc. *cough*.

Ecosystem specific variants

Nix can package and distribute apps, tools and artifacts from almost any ecosystem like Haskell, Javascript, Python, databases etc. Each ecosystem has its own compile tools, build tools, and quirks that we somehow have to embed into Nix. That’s why usually specialized variants of mkDerviation emerge from the Nix community for each ecosystem.

For example, if we want to write a Haskell app, we nowadays use the following variant:

# Example haskellproject.nix file
let
  pkgs = import <nixpkgs> { };
  hPkgs = pkgs.haskellPackages;
in 
hPkgs.developPackage {
  root = ./.;
  # match haskellProject.cabal file
  name = "haskellProject"; 
}
let .. in ..
Custom variables in the let-part to use in the in-part. Allows for more readable code. ➡ Let explanation.
pkgs
Variable with all available Nix packages.
import <nixpkgs> {}
Load all available Nix packages ➡ Import explanation.
haskellPackages
A collection of Nix Haskell packages and helper functions.
developPackage
A Nix helper function to get a list of required Nix Haskell packages from the Haskell-native .cabal file in the project folder ➡ Nix function syntax.
root
Specify the folder where to find the .cabal file and the source code.
./.
Represents the current folder.
haskellProject.cabal
A file listing all the needed Haskell packages (just like Node’s package.json or Java’s pom.xml). ➡ Cabal syntax.

developPackage deals with the quirks of the Haskell compiler Ghc and its build tool Cabal. It hides the huge complexity we would have to deal with if we only had mkDerivation. Similar helper functions exist for other ecosystems.

More: Setup Haskell development environments
See: Nix ecosystems list
See: developPackage source code

Tags: nix derivation package language beginner

Malte Neuss

Java Software Engineer by day, Haskell enthusiast by night.

Other Posts In Series

Just Enough Nix For Programming

How to Setup Programming Environments with Nix Shell

With a few lines you can setup programming environments (compilers, packages, databases, browsers, and other tools from different ecosystems) with having only the Nix Package Manager installed.

Read More

Just Enough Nix For Programming

How to Search For Apps and Tools within Nix Packages

Nix is very capable of managing dependencies and creating programming environments. Everything is bundled up as Nix packages. However, when we look for an app in a specific version, it can be difficult to find the right Nix package name.

Read More

Just Enough Nix For Programming

How to Setup a Haskell Programming Environment with Nix

With a few lines you can setup your Haskell programming environment (compilers, packages, databases, browsers, and other tools from different ecosystems) with having only the Nix Package Manager installed.

Read More