Development Guide
Prerequisites
Section titled “Prerequisites”- 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:
./scripts/dev-setup.shBuilding and Running
Section titled “Building and Running”# Buildjust build
# Run CLIjust run -- --help
# Dev mode (auto-bootstraps Lima + Firecracker)just run -- dev
# Release build (stripped, LTO)just release-buildTesting
Section titled “Testing”# Run all tests with nextestjust test
# Test a single cratejust test-crate mvm-core
# Run tests matching a filterjust test-filter "test_snapshot"
# Full CI gate (lint + test)just ciTest Organization
Section titled “Test Organization”| Location | Type | What it tests |
|---|---|---|
crates/*/src/**/*.rs (#[cfg(test)]) | Unit tests | Internal functions within the crate |
crates/*/tests/*.rs | Integration tests | Public API of each crate |
tests/cli.rs | Binary tests | CLI arg parsing, help output, subcommand structure |
Testing Conventions
Section titled “Testing Conventions”- 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
Linting and Formatting
Section titled “Linting and Formatting”just fmt # Format all codejust clippy # Lint (zero warnings required)just lint # Both format check + clippyStyle Rules
Section titled “Style Rules”- Edition 2024:
usestatements don’t needextern crate; let chains supported - No
clippy::too_many_arguments: never suppress this lint — refactor into a params struct - No
format!()informat!()named args: extract to a variable first - Cross-crate imports: always use
mvm_core::,mvm_runtime::, etc.
Architecture Principles
Section titled “Architecture Principles”Multi-Backend
Section titled “Multi-Backend”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.
Host vs. VM
Section titled “Host vs. VM”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.
Key Patterns
Section titled “Key Patterns”- 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
Adding New Types
Section titled “Adding New Types”When adding fields to structs in serialized state:
- Add
#[serde(default)]to the new field for backward compatibility cargo test --workspaceto find all broken test constructions- Fix each one
- Add a unit test for the new behavior
Developer Workflow Commands
Section titled “Developer Workflow Commands”Beyond the standard build/test/lint cycle, mvmctl provides commands for managing the dev environment:
# First-time setup (installs deps, creates Lima VM, default network)just run -- init
# Image catalog — browse and build images from Nix templatesjust run -- image list # browse bundled catalogjust run -- image search http # search by name/tagjust run -- image fetch minimal # build from catalog entry
# Named dev networksjust run -- network create isolated # create a named networkjust run -- network list # list all networksjust run -- up --flake . --network isolated # attach VM to a network
# Interactive console (PTY-over-vsock, no SSH)just run -- console myvm # interactive shelljust run -- console myvm --command "uname -a" # one-shot exec
# Cache and diagnosticsjust run -- cache info # show cache dir and disk usagejust run -- cache prune # clean stale temp filesjust run -- security status # security posture evaluationjust run -- doctor # dependency checksConsole Access
Section titled “Console Access”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.consolemust betruein the guest security policy) - Single session per VM, 15-minute idle timeout
- Supports both Firecracker and Apple Container backends
XDG Directory Layout
Section titled “XDG Directory Layout”Dev tool state uses XDG-compliant paths (override with MVM_CACHE_DIR, MVM_CONFIG_DIR, etc.):
| Path | Purpose |
|---|---|
~/.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.
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml | Push to main/feat/*, PRs | check, fmt, clippy, test (macOS + Linux), audit |
release.yml | Tags matching v* | Builds 4 platform binaries, creates GitHub Release |
publish-crates.yml | Release published | Publishes to crates.io in dependency order |
pages.yml | Push to main | Deploys docs to GitHub Pages |
Release Process
Section titled “Release Process”# 1. Bump version in root Cargo.toml [workspace.package]# 2. Update CHANGELOG.md# 3. Commit and taggit add -A && git commit -m "release: v0.3.0"git tag v0.3.0
# 4. Push (triggers release.yml)git push && git push --tagsThe deploy guard (scripts/deploy-guard.sh) validates the tag matches the workspace version before publishing.