Building Customised NixOS Images

By Franz Pletz | Tue, 11 Sep 2018

To set up a NixOS system, you usually boot into a live NixOS system and install it onto a local disk as outlined in the manual. You can then modify the system configuration to tailor it to your needs.

The build system Hydra builds live images like ISO images, container tarballs or AMIs based on their definition in nixpkgs. These images are made available for download on the official website.

By default NixOS live images only come with a basic system configuration that provides a minimal set of tools and services needed for the installation process. In this blog post we will show you how we can use Nix to define and build our own bootable live image with a customised system configuration.

Evaluating NixOS Configurations

In order to generate a complete NixOS system closure containing all components necessary to boot the system, we have to evaluate a NixOS system configuration. The system closure is one of the results of the evaluation of a NixOS configuration.

In most places in the nixpkgs repository, the function in nixos/lib/eval-config.nix is used for evaluation but there are convenience wrappers like nixos/default.nix and the nixos function in the top level nixpkgs set. The latter is only available starting with the soon-to-be-released NixOS 18.09.

For instance, to evaluate our local NixOS configuration we could use:

1$ nix-build -E "(import <nixpkgs/nixos> { configuration = /etc/nixos/configuration.nix; }).system"

Or even simpler:

1$ nix-build '<nixpkgs/nixos>' -A system --arg configuration /etc/nixos/configuration.nix

What is happening here? First, we use the built-in import function to evaluate the nixos/default.nix file from nixpkgs. The import of <nixpkgs/nixos> above indicates that we want to use nixpkgs from the NIX_PATH environment variable which usually refers to our default channel.

The result of this expression is an attribute set that contains the system closure, the kernel, the initial ramdisk and possibly other build targets defined by NixOS modules.

If we are on 18.09 or later, we can use the nixpkgs.nixos function like this:

1$ nix-build -E "(import <nixpkgs> {}).nixos /etc/nixos/configuration.nix"

Note that nixpkgs is a function that takes an attribute set with configuration like overlays, overrides or the target platform for cross compiling. This function returns the complete package set.

We call nixpkgs with an empty attribute set which just means that we are happy with the default parameters. Next we call the included function nixos with the local NixOS configuration that is typically located in /etc/nixos.

This function returns the same result as the example above, for 18.03 or older.

Building QEMU VMs

If we use nixos/default.nix to evaluate a NixOS config, some of the convenience attributes to generate an ad-hoc QEMU VM with the provided config. Note that this is primarily intended for testing because the nix store of the host will be mounted into the VM.

1$ nix-build '<nixpkgs/nixos>' -A vm --arg configuration /etc/nixos/configuration.nix

This is equivalent to nixos-rebuild build-vm or nixos-build-vms which you might have used before.

Instead of a simple system closure, we will get a shell script as entry point that can be used to start the VM:

1$ ./result/bin/run-nixos-vm

To build a complete qcow2 image that we can upload and run in a cloud environment, we can use the generic nova-image module:

1$ nix-build '<nixpkgs/nixos>' -A \
2  --arg configuration '<nixpkgs/nixos/maintainers/scripts/openstack/nova-image.nix>'

To customize qcow2 images see the following section on ISO images.

Building ISO Images

To build ISO or similar images, a specialised module that is not imported by default must be included in the configuration. This module will set a few options so the NixOS system will run smoothly on the desired target and expose an attribute in that builds the actual image.

For readability and convenience, we create a Nix file that can be used to build the image. This file can also be evaluated and built on Hydra.

 1# custom-iso.nix
 3{ nixpkgs ? <nixpkgs>, system ? "x86_64-linux" }:
 6  myisoconfig = { pkgs, ... }: {
 7    imports = [
 8      "${nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix"
 9    ];
11    networking.hostName = "mynixos"; services.nginx.enable = true;
12    environment.systemPackages = with pkgs; [ tmux vim ];
13    users.extraUsers.root.password = "mynixos";
14  };
16  evalNixos = configuration: import "${nixpkgs}/nixos" {
17    inherit system configuration;
18  };
20in { iso = (evalNixos myisoconfig); }

We define a function that returns an attribute set that contains the customised iso. As specified in the line 3 the function expects a set of the two attributes, nixpkgs and system, with default values. On Hydra, nixpkgs can just be another input and system can be set to the target platform of our choice.

Beginning in line 6 we define myisoconfig as the system configuration of our iso image. This can be any form of acceptable NixOS configuration or module. We use a function here because we need a reference to pkgs. Alternatively, it can also just be an attribute set.

In line 8 we import the basic configuration and image generation module for bootable iso images and add our custom configuration below as we would in a regular NixOS machine configuration.

In line 16 we define a helper function that evaluates the passed configuration. For more information about its inner workings see nixos/default.nix.

In lines 20 an attribute set is returned which only includes the desired isoImage with our custom system configuration. The iso-image.nix module from line 7 exposes the option. On Hydra, this will create a single iso job.

Using our definition we can build and test our custom iso image. shell $ nix-build custom-iso.nix $ qemu-kvm -cdrom result/iso/nixos.iso

Further Notes & Hacking

You can have a look at nixos/release.nix to see what other images are built and how you can build them yourself. In most cases, you can use the Nix file above that builds ISO images as a starting point.

If you want to improve it, you could start by generalizing myisoconfig so it can be reused for qcow2 images, container tarballs or netboot images.

Note that the resulting images will not contain a default channel, so most nix commands like nix-shell or nix-env will not work out of the box. Just import the nixos/modules/installer/cd-dvd/channel.nix module to copy the nixpkgs you are currently building the image with as default channel.

Do you have a question about Nix?

Just ask us! We would love to talk to you!

Check out our crew