MicroVM.nix
repository·main·Indexed 25 days ago
https://github.com/microvm-nix/microvm.nixA Nix Flake for building and running NixOS as highly isolated MicroVMs using Type-2 Hypervisors on NixOS/Linux or macOS. It supports multiple hypervisors including QEMU, Firecracker, Cloud-hypervisor, Crosvm, Kvmtool, Stratovirt, and vfkit. Features include CPU emulation via QEMU, experimental graphics support, host-internal bridge networking with NAT and port forwarding, and fully declarative VM definitions.
What's inside microvm.nix
- microvm.nix is a Nix Flake designed to run lightweight NixOS virtual machines on NixOS and macOS hosts. It provides a way to provision MicroVMs by leveraging the NixOS configuration system, offering better isolation than containers by running a dedicated guest OS kernel. It utilizes the MicroVM machine type, which optimizes performance by replacing emulated devices with virtio interfaces.
Use systemd services for MicroVM host management
mainWhen running MicroVMs on a NixOS host (rather than interactively from a package), thehostnixosModule provides several systemd services to handle infrastructure setup and lifecycle management. These services automate the creation of network interfaces, PCI passthrough preparation, and the execution of the VMs themselves.Deploy MicroVMs via SSH using microvm.deploy.rebuild
mainThe
microvm.deploy.rebuildinterface provides a high-level workflow similar tonixos-rebuild. It evaluates the configuration locally and then builds/installs it on the remote host.It performs two main steps:
- Runs
microvm.deploy.installOnHostto transfer derivations and build them on the remote system. - Activates the new system by either running
microvm.deploy.sshSwitch(if SSH is running in the MicroVM) or restarting the MicroVM's systemd service on the host.
Note: When calling this command, the SSH addresses for both the host and the MicroVM must be provided before the
switchargument.nix run .#nixosConfigurations.my-microvm.config.microvm.deploy.rebuild root@example.com root@my-microvm.example.com switch- Runs
Enable Rosetta support with vfkit on Apple Silicon
mainTo run x86_64 (Intel) binaries within an ARM64 Linux VM on Apple Silicon Macs, enable Rosetta support in your MicroVM configuration using the
vfkithypervisor. The NixOS module automatically handles mounting the Rosetta virtiofs share and configuringbinfmtto use Rosetta for x86_64 binaries.{ microvm = { hypervisor = "vfkit"; vfkit.rosetta = { enable = true; # Optional: install Rosetta automatically if missing install = true; }; }; }Generate custom operating system hypervisor packages
mainYou can define independent packages to virtualize operating systems other than NixOS. To ensure these packages are compatible with a
microvm.nixhost:- Export the runner package: Your NixOS configurations should export their runner package as
config.microvm.declaredRunner. This allows the package to be used via [declarative MicroVMs] or themicrovmcommand. - Follow the file layout: The runner package must implement the file layout required by the host (see the interface conventions table for specific paths like
bin/microvm-run,share/microvm/tap-interfaces, etc.).
For a reference implementation, see the
microvm-solo5-sptflake.- Export the runner package: Your NixOS configurations should export their runner package as
Configure a writable /nix/store overlay
mainYou can enable an optional writable layer for the Nix store by setting
microvm.writableStoreOverlay.Important Requirements & Caveats:
- The path must be located on a writable filesystem.
- Do not use 9p or virtiofs shares for the overlay. The Linux overlay filesystem requires a compatible filesystem for the upper (writable) layer. You must use a volume (block device) for the overlay path.
- Persistence: The Nix database will forget all built packages after a reboot (it only retains what is needed for the VM's NixOS system). It is recommended to delete and recreate the overlay after MicroVM shutdown or before startup.
To implement this, set the overlay path and create a volume mounted at that same path.
{ config, ... }: { microvm.writableStoreOverlay = "/nix/.rw-store"; microvm.volumes = [ { image = "nix-store-overlay.img"; mountPoint = config.microvm.writableStoreOverlay; size = 2048; }; }]; }Add a MicroVM runner package to your Flake
mainTo make a MicroVM runner permanently available as a package in your Flake outputs, assign the
declaredRunnerfrom yournixosConfigurationto a package attribute.Once added, you can launch the MicroVM using
nix run .#<package-name>.Run a nixosConfiguration immediately
mainYou can run a
nixosConfigurationinteractively for testing purposes directly from your Flake. This allows you to interact with the MicroVM's console.Note: Running this way does not perform preparation for TAP network interfaces or start
virtiofsd. To work around these limitations, you can rely on 9p shares and use QEMU'shostnetwork interfaces.nix run .#nixosConfigurations.my-microvm.config.microvm.declaredRunnerCentralize MicroVM logging with journald
mainYou can centralize MicroVM logs on the host without network transport by sharing the journal directory via
virtiofs.- Set
microvm.machineIdin the MicroVM configuration sojournaldcan identify the host. - Configure a
virtiofsshare inmicrovm.sharesto map the host's journal directory to the MicroVM's/var/log/journal. - On the host, use
systemd.tmpfiles.rulesto create symlinks for each MicroVM's journal under the host's/var/log/journal. - Use
journalctl --mergeon the host to view all logs together.
# MicroVM configuration microvm.shares = [ { source = "/var/lib/microvms/${config.networking.hostName}/journal"; mountPoint = "/var/log/journal"; tag = "journal"; proto = "virtiofs"; socket = "journal.sock"; } ]; # Host configuration systemd.tmpfiles.rules = map (vmHost: let machineId = self.lib.addresses.machineId.${vmHost}; in "L+ /var/log/journal/${machineId} - - - - /var/lib/microvms/${vmHost}/journal/${machineId}" ) (builtins.attrNames self.lib.addresses.machineId);- Set
Set up a network bridge to link TAP interfaces
mainTo make MicroVMs reachable on your local network, you can create a bridge (
br0) that links your host's physical Ethernet port (e.g.,eno1) with the MicroVM's TAP interfaces (e.g.,vm-*).Note: You must adjust the example IP addresses and interface names (
eno1) to match your specific network environment. Thevm-*pattern must match theidspecified in your MicroVM definition.systemd.network.enable = true; systemd.network.networks."10-lan" = { matchConfig.Name = ["eno1" "vm-*"]; networkConfig = { Bridge = "br0"; }; }; systemd.network.netdevs."br0" = { netdevConfig = { Name = "br0"; Kind = "bridge"; }; }; systemd.network.networks."10-lan-bridge" = { matchConfig.Name = "br0"; networkConfig = { Address = ["192.168.1.2/24" "2001:db8::a/64"]; Gateway = "192.168.1.1"; DNS = ["192.168.1.1"]; IPv6AcceptRA = true; }; linkConfig.RequiredForOnline = "routable"; };Use MACVTAP networking (`type = "macvtap"`)
mainMACVTAP interfaces attach to a host's physical network interface, allowing the MicroVM to join the same Ethernet segment with its own MAC address.
Manual Setup
If running a MicroVM interactively from a package, follow these steps:
- Define the parent
LINK(e.g.,eth0) and theID(themicrovm.interfaces.*.id). - Create the interface using
ip l add. - Obtain the interface index.
- Grant user permissions to the tap device.
Configuration via
hostmoduleWhen using the
hostmodule, MACVTAP interfaces are created via systemd service dependencies. You must specify alinkattribute (the parent interface) and amodeattribute (the MACVTAP filtering mode) for each interface.# Parent interface: LINK=eth0 # MACVTAP interface, as specified under microvm.interfaces.*.id: ID=microvm1 # Create the interface sudo ip l add link $LINK name $ID type macvtap mode bridge # Obtain the interface index number IFINDEX=$(cat /sys/class/net/$ID/ifindex) # Grant yourself permission sudo chown $USER /dev/tap$IFINDEX- Define the parent
Pass through USB devices to a MicroVM
mainTo pass through USB devices, add them to the
microvm.deviceslist using thebus = "usb"attribute and specifying thevendoridandproductid.Note: Unlike PCI devices, USB device permissions are NOT set up automatically. You must manually configure host permissions using
udevrules to ensure thekvmgroup (or the appropriate group used by your hypervisor) has access to the device.microvm.devices = [ # Example: RTL2838UHIDIR (Realtek Semiconductor Corp. RTL2838 DVB-T) { bus = "usb"; path = "vendorid=0x0bda,productid=0x2838"; } # Example: Sonoff Zigbee 3.0 USB Dongle Plus (Silicon Labs CP210x UART Bridge) { bus = "usb"; path = "vendorid=0x10c4,productid=0xea60"; } ];