I've been thinking again about using NixOS to build bootable disk images I can
run on a remote qemu/KVM host. I worked out how to build bootable qcow2
disk
images a few years back, which still seems to work, but I
wanted something that worked with flakes which I've since adopted.
I initially found a way using nixos-generators which worked pretty well until I wanted to change the diskSize. This wasn't a big issue, but I realised I didn't really need nixos-generators either.
This is a pretty standard looking configuration.nix
module that describes an
example system to build.
{ config, lib, pkgs, ... }: {
boot.kernelPackages = pkgs.linuxPackages_5_15;
users.users = {
tarn = {
isNormalUser = true;
extraGroups = [ "wheel" ];
password = "";
};
};
environment.systemPackages = with pkgs; [
python310
];
system.stateVersion = "23.05";
}
The configuration for building qcow2
disk images image can be separated into
it's own module called qcow.nix
. The attribute system.build.qcow2
is set to use the NixOS
make-disk-image.nix
magic.
{ config, lib, pkgs, modulesPath, ... }: {
imports = [
"${toString modulesPath}/profiles/qemu-guest.nix"
];
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
autoResize = true;
fsType = "ext4";
};
boot.kernelParams = ["console=ttyS0"];
boot.loader.grub.device = lib.mkDefault "/dev/vda";
system.build.qcow2 = import "${modulesPath}/../lib/make-disk-image.nix" {
inherit lib config pkgs;
diskSize = 10240;
format = "qcow2";
partitionTableType = "hybrid";
};
}
To tie it all together we can create a flake.nix
file which describes an
input and uses the modules above to build an output.
{
description = "Example Virtual Machine Configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs }: {
nixosConfigurations = {
# configuration for builidng qcow2 images
build-qcow2 = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
./qcow.nix
];
};
};
};
}
To run a flake all the configuration needs to be added to a git repository.
$ git init
$ git add .
To build the qcow2
images nix build
is used. Here we use our build_qcow2
configuration and the system.build.qcow2
attribute from earlier which is now
somehow available.
$ nix build .#nixosConfigurations.build-qcow2.config.system.build.qcow2
This builds a qcow2 disk image in the nix store and creates a result
symlink
to the the containing directory in the store. To try out the disk image, we'll
need a writable copy that can be mounted as a writable disk by qemu
.
$ cp result/nixos.qcow2 root.qcow2
$ chmod 644 root.qcow2
The image can now be used to boot a qemu virtual machine
$ qemu-kvm -name nixos \
-m 4G \
-smp 2 \
-drive cache=writeback,file=root.qcow2,id=drive1,if=none,index=1,werror=report \
-device virtio-blk-pci,bootindex=1,drive=drive1 \
-nographic
...
<<< Welcome to NixOS 23.05.20230924.261abe8 (x86_64) - ttyS0 >>>
Run 'nixos-help' for the NixOS manual.
nixos login: tarn
Password:
[tarn@nixos:~]$ uname -a
Linux nixos 5.15.133 #1-NixOS SMP Sat Sep 23 09:10:03 UTC 2023 x86_64 GNU/Linux
[tarn@nixos:~]$ python --version
Python 3.10.12
To kill the VM shut it down with the poweroff
command or use ctrl + a
followed by x
.