Your First MicroVM
This guide walks through writing a Nix flake that builds a microVM image, then booting it with mvmctl.
Understanding the Layers
Section titled “Understanding the Layers”mvmctl auto-selects the best backend for your platform:
Linux (KVM): mvmctl up --> Firecracker microVM (direct)macOS 26+: mvmctl up --> Apple Container (Virtualization.framework)Docker: mvmctl up --> Docker container (universal fallback)macOS <26: mvmctl up --> Lima VM --> Firecracker microVM| Layer | Access command | Has your project files? |
|---|---|---|
| Host | Your normal terminal | Yes |
| Lima VM (macOS <26) | mvmctl dev or mvmctl dev shell | Yes (~ mounted read/write) |
| MicroVM | (headless, no SSH) | No (isolated filesystem) |
MicroVMs are headless workloads with no SSH access — they communicate via vsock only.
Write a Flake
Section titled “Write a Flake”Create a flake.nix in your project:
{ inputs = { mvm.url = "github:auser/mvm?dir=nix"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; };
outputs = { mvm, nixpkgs, ... }: let system = "aarch64-linux"; pkgs = import nixpkgs { inherit system; }; in { packages.${system}.default = mvm.lib.${system}.mkGuest { name = "hello"; packages = [ pkgs.curl ];
services.hello = { command = "${pkgs.python3}/bin/python3 -m http.server 8080"; };
healthChecks.hello = { healthCmd = "${pkgs.curl}/bin/curl -sf http://localhost:8080/"; healthIntervalSecs = 5; healthTimeoutSecs = 3; }; }; };}mkGuest handles everything internally — the kernel, busybox init, guest agent, networking, drive mounting, and service supervision are all built into the image automatically. You just define your services and health checks.
Build and Run
Section titled “Build and Run”# Build the imagemvmctl build --flake .
# Boot a VM (auto-selects best backend)mvmctl up --flake . --cpus 2 --memory 1024
# Or run in background with port forwardingmvmctl up --flake . -d -p 8080:8080Check Status
Section titled “Check Status”# List running VMsmvmctl ls
# View guest console logsmvmctl logs helloRun with Config and Secrets
Section titled “Run with Config and Secrets”Pass custom files to the guest drives:
mkdir -p /tmp/config /tmp/secretsecho '{"port": 8080}' > /tmp/config/app.jsonecho 'API_KEY=sk-...' > /tmp/secrets/app.env
mvmctl up --flake . \ -v /tmp/config:/mnt/config \ -v /tmp/secrets:/mnt/secretsInside the guest, config files appear at /mnt/config/ and secrets at /mnt/secrets/.
mvmctl down helloNext Steps
Section titled “Next Steps”- Writing Nix Flakes — the full
mkGuestAPI - Templates — build once, reuse everywhere
- Config & Secrets — inject files at boot