Skip to content

Development Guide

  • Rust 1.85+ (Edition 2024) — install via rustup
  • macOS or Linux — macOS for development via Lima, Linux for native /dev/kvm
  • Nix (optional) — only needed for building microVM images

Run the bootstrap script on a fresh machine:

Terminal window
./scripts/dev-setup.sh
Terminal window
# Build
just build
# Run CLI
just run -- --help
# Dev mode (auto-bootstraps Lima + Firecracker)
just run -- dev
# Release build (stripped, LTO)
just release-build
Terminal window
# Run all tests with nextest
just test
# Test a single crate
just test-crate mvm-core
# Run tests matching a filter
just test-filter "test_snapshot"
# Full CI gate (lint + test)
just ci
LocationTypeWhat it tests
crates/*/src/**/*.rs (#[cfg(test)])Unit testsInternal functions within the crate
crates/*/tests/*.rsIntegration testsPublic API of each crate
tests/cli.rsBinary testsCLI arg parsing, help output, subcommand structure
  • Unit tests go in #[cfg(test)] mod tests {} at the bottom of the source file
  • CLI binary tests go in root tests/cli.rs
  • Use #[serde(default)] when adding fields to structs used in test fixtures
Terminal window
just fmt # Format all code
just clippy # Lint (zero warnings required)
just lint # Both format check + clippy
  • Edition 2024: use statements don’t need extern crate; let chains supported
  • No clippy::too_many_arguments: never suppress this lint — refactor into a params struct
  • No format!() in format!() named args: extract to a variable first
  • Cross-crate imports: always use mvm_core::, mvm_runtime::, etc.

mvmctl supports four backends: Firecracker (native Linux), Apple Container (macOS 26+), Docker (universal fallback), and microvm.nix (NixOS QEMU). The VmBackend trait in mvm-core abstracts the lifecycle; AnyBackend in mvm-runtime dispatches at runtime. Auto-selection priority: KVM → Apple Container → Docker → Lima + Firecracker.

All Linux operations (networking, Firecracker, cgroups) run inside the Lima VM on macOS <26:

// This runs a bash script inside the Lima VM (or natively on Linux with KVM)
mvm_runtime::shell::run_in_vm("ip link add br-tenant-1 type bridge")?;

On native Linux, run_in_vm runs directly without Lima. On macOS 26+, the Apple Container backend handles VM lifecycle natively.

  • Idempotent operations: every setup step checks if already done before acting
  • Config drive for metadata: instance metadata delivered via read-only ext4 disk
  • Vsock over SSH: guest communication uses vsock, not sshd (all backends)
  • Same rootfs everywhere: Nix-built ext4 images work on all backends

When adding fields to structs in serialized state:

  1. Add #[serde(default)] to the new field for backward compatibility
  2. cargo test --workspace to find all broken test constructions
  3. Fix each one
  4. Add a unit test for the new behavior

Beyond the standard build/test/lint cycle, mvmctl provides commands for managing the dev environment:

Terminal window
# First-time setup (installs deps, creates Lima VM, default network)
just run -- init
# Image catalog — browse and build images from Nix templates
just run -- image list # browse bundled catalog
just run -- image search http # search by name/tag
just run -- image fetch minimal # build from catalog entry
# Named dev networks
just run -- network create isolated # create a named network
just run -- network list # list all networks
just run -- up --flake . --network isolated # attach VM to a network
# Interactive console (PTY-over-vsock, no SSH)
just run -- console myvm # interactive shell
just run -- console myvm --command "uname -a" # one-shot exec
# Cache and diagnostics
just run -- cache info # show cache dir and disk usage
just run -- cache prune # clean stale temp files
just run -- security status # security posture evaluation
just run -- doctor # dependency checks

microVMs have no SSH. Interactive access is via mvmctl console which uses PTY-over-vsock:

  • Authenticated via the existing Ed25519 vsock protocol
  • Dev-mode only (access.console must be true in the guest security policy)
  • Single session per VM, 15-minute idle timeout
  • Supports both Firecracker and Apple Container backends

Dev tool state uses XDG-compliant paths (override with MVM_CACHE_DIR, MVM_CONFIG_DIR, etc.):

PathPurpose
~/.cache/mvm/Build artifacts, images, VM runtime state
~/.config/mvm/User config (config.toml)
~/.local/state/mvm/Logs, audit trail
~/.local/share/mvm/Templates, network definitions, VM name registry

Legacy ~/.mvm/ paths are auto-detected as fallback.

WorkflowTriggerWhat it does
ci.ymlPush to main/feat/*, PRscheck, fmt, clippy, test (macOS + Linux), audit
release.ymlTags matching v*Builds 4 platform binaries, creates GitHub Release
publish-crates.ymlRelease publishedPublishes to crates.io in dependency order
pages.ymlPush to mainDeploys docs to GitHub Pages
Terminal window
# 1. Bump version in root Cargo.toml [workspace.package]
# 2. Update CHANGELOG.md
# 3. Commit and tag
git add -A && git commit -m "release: v0.3.0"
git tag v0.3.0
# 4. Push (triggers release.yml)
git push && git push --tags

The deploy guard (scripts/deploy-guard.sh) validates the tag matches the workspace version before publishing.