Lightweight MicroVMs with microvm.nix

Updated

September 12, 2026

Lightweight MicroVMs with microvm.nix

Full libvirt guests take minutes to iterate. The boring default for local multi-node tests is: microvm.nix (or runNixOSTest) on nixpkgs 26.05; production still gets real machines or the capstone VMs.

Mental model

microvm.nix is a NixOS module that boots a guest in hundreds of milliseconds, often sharing the host store via virtiofs. It is a lab accelerator, not a tenant isolator. Do not put untrusted workloads there without reading the project’s trust model.

host flake
  nixosConfigurations.desk-micro
    microvm.hypervisor = "qemu"   # or cloud-hypervisor
    microvm.mem / vcpu
    volumes you listed — nothing else persists

runNixOSTest is the CI twin: headless, assertions, no SSH session. Keep both only when they answer different questions (human poke vs PR gate).

/dev/kvm must exist on the host. Nested virt on a cloud VM is often off — that is a missing feature, not a Nix bug.

Worked examples

Case 1: Guest in the flake

Save as flake.nix:

# flake.nix
{
  description = "Desk microVM lab";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    microvm.url = "github:astro/microvm.nix";
    microvm.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { self, nixpkgs, microvm }: {
    nixosConfigurations.desk-micro = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        microvm.nixosModules.microvm
        {
          microvm.hypervisor = "qemu";
          microvm.mem = 1024;
          microvm.vcpu = 2;
          networking.hostName = "desk-micro";
          services.openssh.enable = true;
          services.openssh.settings.PasswordAuthentication = false;
          users.users.root.openssh.authorizedKeys.keys = [
            "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI… deskadmin@ops-01"
          ];
          system.stateVersion = "26.05";
        }
      ];
    };
  };
}

follows keeps the guest on the same 26.05 as the host flake.

Case 2: Run the declared runner

nix run .#nixosConfigurations.desk-micro.config.microvm.declaredRunner

(The exact run attr is in microvm.nix docs for your pin.) You should get a console. Log in, then:

hostname
systemctl is-system-running
systemctl status sshd --no-pager
halt

If QEMU exits immediately, check ls -l /dev/kvm and that your user is in kvm.

ls -l /dev/kvm
id -nG | tr ' ' '\n' | grep -x kvm || echo 'not in kvm group — log out after extraGroups'

Nested virt on a cloud workstation: the hypervisor UI must enable it. ls /dev/kvm missing is not a Nix eval error.

Case 3: Persist nothing unless listed

A default microVM is as ephemeral as a tmpfs root. If a test needs /persist, declare a volume in the module:

# volumes.nix
{
  microvm.volumes = [
    {
      image = "persist.img";
      mountPoint = "/persist";
      size = 1024;
    }
  ];
}

Otherwise the next nix run starts clean — which is what you want for a lab.

Case 4: Host virtualisation (workstation)

Save as hosts/ops-lab.nix (fragment of the workstation):

# hosts/ops-lab.nix
{
  virtualisation.libvirtd.enable = true;
  users.users.deskadmin.extraGroups = [ "libvirtd" "kvm" ];
}
ls -l /dev/kvm
groups

kvm in the group list, crw-rw---- on /dev/kvm. Nested virt on a cloud workstation: enable it in the hypervisor UI or pick a metal builder.

Case 5: microVM vs runNixOSTest

Need Tool
SSH in, poke Caddy, read journals microVM on the laptop
PR must fail if nginx does not listen pkgs.testers.runNixOSTest in checks
Production desk-api Colmena to real hosts

Do not maintain both for the same scenario without a reason. CI does not SSH into your laptop guest.

The trap

The trap is running production desk-api in a microVM on someone’s laptop. Prod is Colmena to real hosts (or the capstone VMs). microVMs die when the lid closes.

The boring rule

  • 26.05 + microvm.inputs.nixpkgs.follows.
  • QEMU/KVM for labs. runNixOSTest for CI.
  • No production tenants on a laptop hypervisor.
  • stateVersion = "26.05" on new guests.
  • Share the store only when you understand the trust model. Volumes you did not list do not exist.

Try this

  1. Enable KVM, run Case 1, hostname inside the guest.
  2. systemctl status sshd inside it, then halt.
  3. Compare wall-clock boot to a libvirt NixOS guest on the same box.
  4. Add a checks.x86_64-linux runNixOSTest that covers the same sshd enablement so CI does not need the laptop.