MicroVM.nix

repository·main·Indexed 25 days ago

https://github.com/microvm-nix/microvm.nix

A 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.

Tokens
15.1K
Snippets
47
Records
72
Agent score
78%

What's inside microvm.nix

  1. Overview of microvm.nix

    main
    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.
  2. Use systemd services for MicroVM host management

    main
    When running MicroVMs on a NixOS host (rather than interactively from a package), the host nixosModule 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.
  3. Deploy MicroVMs via SSH using microvm.deploy.rebuild

    main

    The microvm.deploy.rebuild interface provides a high-level workflow similar to nixos-rebuild. It evaluates the configuration locally and then builds/installs it on the remote host.

    It performs two main steps:

    1. Runs microvm.deploy.installOnHost to transfer derivations and build them on the remote system.
    2. 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 switch argument.

    nix run .#nixosConfigurations.my-microvm.config.microvm.deploy.rebuild root@example.com root@my-microvm.example.com switch
  4. Enable Rosetta support with vfkit on Apple Silicon

    main

    To run x86_64 (Intel) binaries within an ARM64 Linux VM on Apple Silicon Macs, enable Rosetta support in your MicroVM configuration using the vfkit hypervisor. The NixOS module automatically handles mounting the Rosetta virtiofs share and configuring binfmt to use Rosetta for x86_64 binaries.

    {
      microvm = {
        hypervisor = "vfkit";
    
        vfkit.rosetta = {
          enable = true;
          # Optional: install Rosetta automatically if missing
          install = true;
        };
      };
    }
  5. Generate custom operating system hypervisor packages

    main

    You can define independent packages to virtualize operating systems other than NixOS. To ensure these packages are compatible with a microvm.nix host:

    1. 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 the microvm command.
    2. 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-spt flake.

  6. Configure a writable /nix/store overlay

    main

    You can enable an optional writable layer for the Nix store by setting microvm.writableStoreOverlay.

    Important Requirements & Caveats:

    1. The path must be located on a writable filesystem.
    2. 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.
    3. 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;
      }; }];
    }
  7. Run a nixosConfiguration immediately

    main

    You can run a nixosConfiguration interactively 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's host network interfaces.

    nix run .#nixosConfigurations.my-microvm.config.microvm.declaredRunner
  8. Centralize MicroVM logging with journald

    main

    You can centralize MicroVM logs on the host without network transport by sharing the journal directory via virtiofs.

    1. Set microvm.machineId in the MicroVM configuration so journald can identify the host.
    2. Configure a virtiofs share in microvm.shares to map the host's journal directory to the MicroVM's /var/log/journal.
    3. On the host, use systemd.tmpfiles.rules to create symlinks for each MicroVM's journal under the host's /var/log/journal.
    4. Use journalctl --merge on 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);
  9. Set up a network bridge to link TAP interfaces

    main

    To 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. The vm-* pattern must match the id specified 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";
    };
  10. Use MACVTAP networking (`type = "macvtap"`)

    main

    MACVTAP 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:

    1. Define the parent LINK (e.g., eth0) and the ID (the microvm.interfaces.*.id).
    2. Create the interface using ip l add.
    3. Obtain the interface index.
    4. Grant user permissions to the tap device.

    Configuration via host module

    When using the host module, MACVTAP interfaces are created via systemd service dependencies. You must specify a link attribute (the parent interface) and a mode attribute (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
  11. Pass through USB devices to a MicroVM

    main

    To pass through USB devices, add them to the microvm.devices list using the bus = "usb" attribute and specifying the vendorid and productid.

    Note: Unlike PCI devices, USB device permissions are NOT set up automatically. You must manually configure host permissions using udev rules to ensure the kvm group (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"; }
    ];