Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

nix

This notes assume to have the experimental-features = nix-command flakes enabled.

nix.conf (ref)

nix config show
# experimental-features
#     enable additional experimental features
# flake-registery
#     url of global registry
# tarball-ttl
#     time the downloaded tarball is treated as valid

nix search (ref)

nix search <installable> <regex>
# Flakeref from registry.
nix search nixpkgs glibc
# Explicit flakeref.
nix search nixpkgs/nixos-25.05 glibc

nix run (ref)

nix run <installable> -- <args>
nix run nixpkgs#strace -- -o strace.txt /bin/ls
nix run nixpkgs/nixos-25.05#emacs

nix shell (ref)

# Enter interactive shell with pkgs in PATH.
nix shell <installable> [<installable>]
nix shell nixpkgs#qemu nixpkgs#zig nixpkgs/nixos-25.05#strace

nix profile (ref)

The history allows to install packages, update them and easily rollback.

# Install/remove/upgrade package(s) (creates a new history version).
nix profile install <installable>
nix profile remove
nix profile upgrade --all

# List installed packages.
nix profile list

# Show different hisitory versions.
nix profile history

# Rollback profile history.
nix profile rollback [--to N]

nix store (ref)

# Show all alive store paths (paths with active reference).
nix-store --gc --print-live
# Show all dead store paths (paths w/o active reference).
nix-store --gc --print-dead

# Run garbage collection on nix store. This deletes all paths which are no
# gcroots and any dependency of any gcroot.
nix store gc

# Delete single path from store.
nix store delete <path>

# gcroots are for example created automatically when installing packages into
# the profile or when building a flake locally.
# gcroots are located under /nix/var/nix/gcroots
#
# One can also create mamual gcroots (eg for tmp operation).
> nix-store --gc --print-dead | grep 'binutils-2.44$'
/nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44

> mkdir -p /nix/var/nix/gcroots/tmp
> ln -s /nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44 /nix/var/nix/gcroots/tmp/keep-binutils

> nix-store --gc --print-dead | grep 'binutils-2.44$'
> nix-store --gc --print-live | grep 'binutils-2.44$'
/nix/store/wcv8n2k53w8sbffpf716gxj6mjb5jf26-binutils-2.44

nix flake (ref)

Flakes provide a description for reproducible builds.

# Create a new flake in a folder with name.
nix flake new <name>
# Create a new flake from given template.
nix flake new -t <template> <name>
# Create a new flake with the c-hello output from the templates flake.
nix flake create -t templates#c-hello myflake

# Show the outputs of a flake
nix flake show <flake>
# Show outputs from the default "templates" flake.
nix flake show templates
# Assumes '.' as flake (which effectively means flake.nix in cwd).
nix flake show

# Show the derivation of an installable in pretty format.
nix derivation show <installable>
# Show the derivation of the glibc output from the nixpkg flake.
nix derivation show nixpkgs#glibc
# Assumes '.' as flake.
nix derivation show

# Show a flakes metadata (eg store path, inputs, description, ..)
nix flake metadata <flake>

# Update the versions in the flake.lock file.
nix flake update

Use nix registry list to list available global flakes or nix registry pin <flake>, to pin a flake to a fixed version.

Documentation for installable syntax.

a bare bones flake

The following shows a bare bones flake using the builtins.derivation function to define an output called moose. The builtin provides an empty environment.

In general, a derivation is a declarative description of how to run an executable (builder) on a set of inputs (input derivations aka dependencies) to produce a set of outputs (store paths).

# The flake is effectively an attribute set with a standard set of attributes.
{
  description = "bare bones (flake)";

  # Input(s) of the flake.
  # Each input attribute is passed into the output function below.
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  # The 'outputs' attribute is a function, accepting an attribute set with a
  # self reference and all the inputs specified above.
  # The function shall return an attribute set with different outputs.
  # There are standardized outputs for different commands such as:
  #   > nix build
  #   > nix develop
  outputs = { self, nixpkgs }: let
    # Define local variables with let .. in construct.
    system = "x86_64-linux";
    # Import another flake, passing an attribute set.
    # inherit system, is effectively system = system;
    pkgs = import nixpkgs { inherit system; };
  in
  # Return attribute set.
  {
    # Some output.
    moose = builtins.derivation {
      # -- Required attributes.
      inherit system;
      name = "bone";
      builder = "${pkgs.bash}/bin/bash";
      # -- Optional attributes.
      args = [ "-c" "echo hello > $out" ];
    };
  };
}

Understanding this example is crucial as any abstractions such as the nixpkgs.stdenv builds on top of this.

One can inspect and build the derivation as follows.

# Show the derivation for the output "moose".
> nix derivation show .#moose
{
  "/nix/store/q2l7g7myy5gmks7jml6hz2jd73yybplq-bone.drv": {
    ...
    "inputDrvs": {
      "/nix/store/d2gv7y7i7bw79dpfskzzk2g4h6mc0677-bash-interactive-5.2p37.drv": {
}

# Build the output 'moose' for the flake.nix in the cwd ('.').
> nix build .#moose ; cat result
hello

A derivation also defines a set of outputs, which by default is ["out"]. Outputs are passed as environment variables to the builder, from where they can be used as install targets for example.

An interesting point to observe is that nix computed bash as input derivation (inputDrv) and hence as input dependency for this output.

a stdenv flake

The stdenv library from the nixpkgs flake provides the mkDerivation function to simplify building packages (defining derivations). Many tools commonly used to build software are available out of the box. Specifying build and runtime dependencies can also be done easiy.

The default builder provided by the mkDerivation function, organizes the build into multiple phases like config, build and install. When defining the derivation these phases can be easily overwritten.

Alternatively there is stdenvNoCC.mkDerivation which provides an environment without a C compiler.

The example show a simple build and installation of a C file.

{
  description = "stdenv (flake)";

  inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
      # Enable support for 'nix fmt'.
      formatter.${system} = pkgs.nixfmt;

      # This is the default output that will be build & run if no output is
      # specified, when running the following in the flakes directory.
      # > nix build
      # > nix run
      # > nix log
      packages.${system}.default = pkgs.stdenv.mkDerivation {
        # Either specify 'name' or 'pname & version'.
        # In the latter case, 'name' will be ${pname}-${version}.
        pname = "test";
        version = "0.1";

        # There are different builtins to also unpack or fetch tarballs.
        src = ./src;

        # The default builder of the stdenv defines many different phases that
        # can be overwritten.

        buildPhase = ''
          gcc -O2 -g -o test test.c
        '';

        installPhase = ''
          mkdir -p $out/bin
          cp test $out/bin
        '';
      };
    };
}

Use NIX_DEBUG=[0-7] to enable stdenv debug logging. Use nix log [<installable>] to inspect the build log.

One can also define multiple output directories using the outputs attribute in the derivation. Each output turns into an environment variable of the same name. Use nix derivation show to inspect the outputs and the environment the builder inherits.

For example the glibc package in the nixpkgs flake provides multiple outputs.

> nix derivation show nixpkgs#glibc
..
"outputs": {
      ..
      "out": {
        "path": "/nix/store/g3s0z9r7m1lsfxdk8bj88nw8k8q3dmmg-glibc-2.40-66"
      },
      "static": {
        "path": "/nix/store/lfj0w1r0ppj2swb11pz8vkppmsy1rf6y-glibc-2.40-66-static"
      }
    },
..

a development shell flake

The nixpkgs flake also provides the mkShell derivation function (specialization of mkDerivation), which can be used to easily define a development environment.

Alternatively there is also mkShellNoCC which provides an environment without a C compiler.

The example shows an environment with the zig compiler and zls, the zig lsp server. Running nix develop will drop into a shell where these packages are available.

{
  description = "dev shell (flake)";

  inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; };

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [
          pkgs.zig
          pkgs.zls
        ];
        # https://github.com/NixOS/nixpkgs/issues/270415
        shellHook = ''
          unset ZIG_GLOBAL_CACHE_DIR
        '';
      };
    };
}

When launching a development shell, the environment of the current shell is inherited. Using -i clears the environment when entering the dev shell, using -k certain variables can be kept. For example the following command will only inherit $USER, $HOME, $DISPLAY into the development shell.

nix develop -i -k USER -k HOME -k DISPLAY

One can also just run a command in the development shell.

nix develop --command bash -c "mkdir build && cd build && cmake .. && make"

flake default targets

When called without an explicit output, certain nix commands have default outputs. The following gives the most relevant.

nix build
nix run
nix log
# uses -> packages.<SYSTEM>.default

nix develop
# uses -> devShells.<SYSTEM>.default

a flake for multiple systems

The nixpkgs flake also provides lots of useful library functions. One example is the nixpkgs.lib.genAttrs function, which allows to create an attribute set from a list of keys and a lambda function which computes the values of the attribute set.

nix-repl> pkgs.lib.genAttrs [ "a" "b" ] (arg: "some-value-${arg}")
{
  a = "some-value-a";
  b = "some-value-b";
}

This can be used to build a flake to support multiple systems. The following shows a small example for two systems which defines a dev shell and a formatter.

{
  description = "a basic flake for multiple systems";

  inputs.nixpkgs.url = "nixpkgs";

  outputs =
    { self, nixpkgs }:
    let
      # forSystems is a function which takes a lambda (String -> Any).
      # genAttrs will call the lambda for each list element and finally return
      # an attribute set where the keys are the values in the list and the
      # values are the return values from the lambda invocation.
      forSystems = nixpkgs.lib.genAttrs [
        "x86_64-linux"
        "aarch64-linux"
      ];

      # pkgs is a function which takes a system as argument and imports the
      # nixpkgs for the given system.
      pkgs = system: import nixpkgs { inherit system; };
    in
    {
      # Generate formatter.${system} attribute set for each supported system.
      formatter = forSystems (system: (pkgs system).nixfmt);

      # Generate devShells.${system}.default attribute set for each supported
      # system.
      devShells = forSystems (system: {
        default =
          # The with statement brings the result of calling (pkgs system) into
          # scope for the next expression.
          with (pkgs system);
          mkShell {
            packages = [
              strace
            ]
            # An example how to customize per system.
            ++ nixpkgs.lib.optionals (system == "x86_64-linux") [ gdb ]
            ++ nixpkgs.lib.optionals (system == "aarch64-linux") [ lldb ];
          };
      });
    };
}

a flake for multiple systems from scratch

The following shows how to build the nixpkgs.lib.genAttrs utility from scratch with nix builtins. This mainly serves as learning but may come in handy when doing work without nixpkgs.

{
  description = "a flake for multiple systems from scratch";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs =
    { self, nixpkgs }:
    let
      systems = [
        "x86_64-linux"
        "aarch64-linux"
      ];

      # "forSystems" is a function, which takes as argument a function.
      # It then calls the function passed for each value in the list "systems",
      # and passes the nixpkgs imported for the given system.
      # Finally it returns an attribute set where each key is the system in the
      # "systems" list and the value is what is returned from the invoked user
      # function.
      #
      # The pseudo code below shows the output.
      # {
      #   systems[0] = f pkgs;
      #   systems[1] = f pkgs;
      #   ...
      # }
      #
      # The "listToAttrs" builtin turns a list of name / value pairs (attribute
      # sets) into an attribute set.
      # > listToAttrs [ { name = "A"; value = "a"; } ... ] -> { A = "a"; ... }
      #
      # The "map" builtin takes a function and a list, then calls the function
      # on each list value and replaces the value with the result of the function.
      # > map (v: "m${v}") [ "a" "b" ... ] -> [ "ma" "mb" ... ]
      forSystems =
        f:
        builtins.listToAttrs (
          builtins.map (system: {
            name = system;
            value = f (import nixpkgs { inherit system; });
          }) systems
        );
    in
    {
      formatter = forSystems (pkgs: pkgs.nixfmt);

      devShells = forSystems (pkgs: {
        default = pkgs.mkShell {
          packages = [
            pkgs.strace
          ];
        };
      });
    };
}

nix lang basics

Nix is a functional language, where everything is an expression. The following shows enough nix lang to come quite far.

$ nix repl

nix-repl> 1 + 2
3

# String interpolation.
# (Assignment operation special to repl).
nix-repl> foo = "bar"
nix-repl> "hello ${foo}"
"hello bar"

# Attribute sets (kv).
nix-repl> { a = 1; b = 2; }
{
  a = 1;
  b = 2;
}
# Joining attribute sets.
nix-repl> { a = 1; } // { b = 2; }
{
  a = 1;
  b = 2;
}


# List, elements are separated by space.
nix-repl> [ 1 2 ]
[
  1
  2
]
# Joining lists.
nix-repl> [ 1 ] ++ [ 2 ]
[
  1
  2
]

# Simple function with argument.
nix-repl> f = a: a + 2
nix-repl> f 4
6

# Function taking an attribute set (? for default args).
nix-repl> f = { a, b ? 2 }: a + b
nix-repl> f { a = 1; b = 2; }
3
nix-repl> f { b = 1; a = 2; }
3
nix-repl> f { a = 1; }
3

# Function taking an attribute set w/o explicitly naming each input.
# Here, inp will bind to the whole input attribute set.
nix-repl> f = { a, b, ... } @ inp : a + inp.b + inp.c
nix-repl> f { b = 1; a = 2; c = 3; }
6

# Defining local variables with "let .. in".
nix-repl> let x = 1; in x + 3
4
nix-repl> x
error: undefined variable 'x'

# Let .. in expression returning an attribute set (handy when defining derivations).
nix-repl> let x = "abc"; in { name = "bar ${x}"; }
{ name = "bar abc"; }

# With to bring names into scope for the next expression.
nix-repl> x = { a = 1; b = 2; }
nix-repl> with x; [ a b ]
[
  1
  2
]

# Show all builtin functions.
nix-repl> builtins
{
  abort = «primop abort»;
  add = «primop add»;
  ..

# Factoring out code into multiple files.
# default.nix
#   { x, y }: {
#       a = x + 10;
#       b = y + 20;
#   }
#
# Import nix file by name ..
nix-repl> myfn = import ./default.nix
# .. or use short form for default.nix.
nix-repl> myfn = import ./.
nix-repl> myfn { x = 1; y = 2; }
{ a = 11; b = 22; }
nix-repl> x = 10
# 'inherit x;' is equal to 'x = x;' in the following.
nix-repl> myfn { inherit x ; y = 2; }
{ a = 20; b = 22; }

The import builtin reads a nix expression from a file and evaluates it to return its value. For example import ./default.nix { x = 1; y = 2; } is equivalent to (import ./foo.nix) { x = 1; y = 2; }. First import reads and evaluates the nix expression in foo.nix, which returns the function (the value), which is then called with the attribute set as input.

useful builtins

# The "map" builtin takes a function and a list, then calls the function on
# each list value and replaces the value with the result of the function.
nix-repl> builtins.map (val: "some val ${val}") ["a" "b"]
[
  "some val a"
  "some val b"
]

# The "listToAttrs" builtin takes a list of name / value pairs (attribute sets)
# and turns the list into a attribute set build from the name / value pairs.
nix-repl> builtins.listToAttrs [{ name = "A"; value = "a"; } { name = "B"; value = "b"; }]
{
  A = "a";
  B = "b";
}

access flakes in the repl

# Access a flake inside repl.
nix-repl> pkgs = builtins.getFlake "nixpgs"
nix-repl> pkgs.legacyPackages.x86_64-linux.stdenvNoCC.mkDerivation
nix-repl> pkgs.legacyPackages.x86_64-linux.llvm  # tab
pkgs.legacyPackages.x86_64-linux.llvm                 pkgs.legacyPackages.x86_64-linux.llvmPackages_git
pkgs.legacyPackages.x86_64-linux.llvm-manpages        pkgs.legacyPackages.x86_64-linux.llvmPackages_latest
pkgs.legacyPackages.x86_64-linux.llvmPackages         pkgs.legacyPackages.x86_64-linux.llvm_12

# Alternatively flakes can be put into the lookup path.
#   arg:  nix repl -I nixpkgs=flake:nixpkgs
#   env:  NIX_PATH=nixpkgs=flake:nixpkgs
#   conf: extra-nix-path = nixpkgs=flake:nixpkgs

# Print the current nix path.
nix-repl> :p builtins.nixPath

type example

The following gives an example how to read the type definitions commonly found in the documentation. For that the type of the nixpkgs.lib.genAttrs function is explored.

genAttrs :: [ String ] -> (String -> Any) -> AttrSet

# genAttrs is the name of the function.
# It takes as arguments
# - a list of strings - [ Strings ]
# - and a function which maps strings to anything - (String -> Any)
# The function then returns an attribute set.

References