Skip to main content


Making GNU Guix VM (the hackish way)


GNU Guix can take a system definition (in .scm file) and make a VM out of it for you. Cool! But that's a "thin" VM — it shares part of the filesystem (the /gnu/store stuff) with the host. If you want a full VM with Guix in it, the official way is to boot the installation .iso inside a VM and use it to perform a usual install.

If you're like me, you probably feel this desire to avoid booting a .iso and building/downloading stuff in a VM ;)

Here's a solution ^^ First, make sure you have qemu and parted available (e.g. with `guix shell qemu parted`). Then, do


qemu-img create -f qcow2 vm.qcow2 100G
sudo modprobe nbd max_part=63
sudo qemu-nbd -n -c /dev/nbd0 vm.qcow2
sudo parted /dev/nbd0 mktable msdos
sudo parted /dev/nbd0 mkpart primary 0% 100%
sudo mkfs.ext4 -L my-vm-root /dev/nbd0p1
sudo mount /dev/nbd0p1 /mnt


Now, we can do `guix system init` to /mnt. But wait! Guix is going to check if the bootloader partition specified in your OS definition (assume it is in ./vm.scm, ok?) really exists (and, by default, it'll try to install the bootloader to it). So if your ./vm.scm has sth like this


(bootloader (bootloader-configuration
(bootloader grub-bootloader)
(targets (list "/dev/sda"))))


It might cause you trouble… My solution is to pick the target depending on an environment variable 💡️ In your ./vm.scm, before the operating-system record is instantiated, put


(define bootloader-target
(or (getenv "SYSTEM_INIT_DISK_DEV") "/dev/sda"))


and make the bootloader part look like


(bootloader (bootloader-configuration
(bootloader grub-bootloader)
(targets (list bootloader-target))))


Now, you can finish the installation :)


sudo sh -c 'SYSTEM_INIT_DISK_DEV=/dev/nbd0 guix system init vm.scm /mnt/'
sudo umount /mnt
sudo qemu-nbd -d /dev/nbd0


That should do. You can start the VM with sth like


sudo qemu-system-x86_64 -net nic,model=rtl8139 \
-net user,hostfwd=tcp::22-:22 \
-m 2G -hda vm.qcow2 -nographic -enable-kvm


The hostfwd part is to expose VM's SSH port on localhost (since you're going to play with it this way, aren't you?). sudo is purely to have permissions to do this forwarding and -nographic is to… If you're still reading, you surely understand this stuff, anyway ;)

In case you're wondering — the environment-controlled bootloader target now allows you to reuse the same system definition file for reconfiguring the running VM (otherwise, there's no reason not to just have /dev/nbd0 hardcoded in OS definition).

While doing this (as part of a more complex project for university classes, btw) I was also (un)lucky enough to stumble upon and fix one bug[1] that broke `guix init` 🦸

[1] https://issues.guix.gnu.org/70245

EDIT 27.04.2024: More or less the same can be achieved using an existing Guix command: `guix system init`. I'm not sure why I overlooked it here (I've been using it before).

This post is licensed CC0 v1.0.