Building Customised NixOS Images
By Franz Pletz | Tue, 9. 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 config.system.build.novaImage \
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 system.build
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
2
3{ nixpkgs ? <nixpkgs>, system ? "x86_64-linux" }:
4
5let
6 myisoconfig = { pkgs, ... }: {
7 imports = [
8 "${nixpkgs}/nixos/modules/installer/cd-dvd/iso-image.nix"
9 ];
10
11 networking.hostName = "mynixos"; services.nginx.enable = true;
12 environment.systemPackages = with pkgs; [ tmux vim ];
13 users.extraUsers.root.password = "mynixos";
14 };
15
16 evalNixos = configuration: import "${nixpkgs}/nixos" {
17 inherit system configuration;
18 };
19
20in { iso = (evalNixos myisoconfig).config.system.build.isoImage; }
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 system.build.isoImage
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.