Getting Started
Your First MicroVM
Write a Nix flake and boot a 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 machine run --> Firecracker microVM (direct)macOS 26+ (AS): mvmctl machine run --> HVF microVM (Hypervisor.framework, vsock-only)macOS 13-25 (AS): mvmctl machine run --> libkrun microVM| Layer | Access | Has your project files? |
|---|---|---|
| Host | Your normal terminal | Yes |
Builder VM (--flake builds only) | Headless — runs nix build on your behalf, no shell | Staged in for the build only |
| MicroVM (workload) | mvmctl machine console (dev-tier images) or mvmctl machine run -it | Only what you explicitly --mount |
MicroVMs are headless workloads with no SSH access — they communicate via vsock only. The builder VM is headless too — it auto-bootstraps the first time you run mvmctl machine build or mvmctl machine run --flake ...; mvmctl bootstrap pre-fetches its image ahead of time and mvmctl doctor reports the resolved builder backend.
Scaffold a project
Section titled “Scaffold a project”The fastest path is mvmctl init, which writes both a mvm.toml (sizing/profile) and a flake.nix (rootfs/kernel content) for you:
mvmctl init hello # creates ./hello/cd hello$EDITOR mvm.toml # tweak vcpus / mem if you like$EDITOR flake.nix # add your servicesThe rest of this guide writes the flake by hand to show how mkGuest works underneath.
The plan-38 manifest model is shipped. Older docs that reference
mvmctl template create/build/…are stale; thetemplatenamespace was removed outright. See the Manifests guide for the current flow.
Write a Flake
Section titled “Write a Flake”Create a flake.nix in your project (or edit the one mvmctl init produced):
{ inputs = { mvm.url = "github:tinylabscom/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”With a mvm.toml next to flake.nix:
# Build (manifest discovered from cwd)mvmctl machine build
# Boot (auto-selects best backend)mvmctl machine run --manifest .
# Or declare signed ingress before bootmvmctl machine run --manifest . --name my-vm --port 8080:8080Without a mvm.toml (just a flake), pass --flake explicitly — that legacy path still works:
mvmctl machine build --flake .mvmctl machine run --flake . --cpus 2 --memory 1024Check Status
Section titled “Check Status”# List running VMsmvmctl machine ls
# View guest console logsmvmctl machine 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 machine run --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 machine stop helloNext Steps
Section titled “Next Steps”- Writing Nix Flakes — the full
mkGuestAPI - Manifests — the
mvm.tomluser model (init → build → up) - Config & Secrets — inject files at boot