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: mvmctl up --> libkrun microVM (Hypervisor.framework)Docker: mvmctl up --> Docker container (Tier 3 fallback)| Layer | Access command | Has your project files? |
|---|---|---|
| Host | Your normal terminal | Yes |
| Dev VM (macOS) | 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.
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 build
# Boot (auto-selects best backend)mvmctl up
# Or run in background with port forwardingmvmctl up -d -p 8080:8080Without a mvm.toml (just a flake), pass --flake explicitly — that legacy path still works:
mvmctl build --flake .mvmctl up --flake . --cpus 2 --memory 1024Check 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 - Manifests — the
mvm.tomluser model (init → build → up) - Config & Secrets — inject files at boot