Host daemon (macOS/HVF or Linux/KVM) that boots a Linux microVM via libkrun and
registers a host UNIX socket ↔ in-VM minimald vsock bridge, so the minimal
CLI can reach a Linux session daemon without knowing a VM exists. Specs:
docs/specs/01-spec-minvmd-host-daemon/ (daemon) and
docs/specs/02-spec-minvmd-linux-kvm/ (Linux/KVM).
minimald runs the guest as pid-1, shipped as the initramfs /init (a cpio of
the cross-compiled static binary) rather than baked into the rootfs. The guest
boots from three artifacts:
- kernel — the upstream
virtio-kernel-rawpackage's uncompressedImage(virtio-kerneloutput). - rootfs — the upstream
microvm-rootfspackage's generic ext4 image (minvmd-rootfsoutput), loaded as a block device (krun_add_disk2→/dev/vda); the initramfs/initmounts it andchroots in. - initramfs —
scripts/build-initramfs.shcross-compilesminimaldto a static aarch64 binary and packs it as the cpio/init.
Prereqs:
- Apple Silicon Mac with Docker (for the
crossmusl toolchain). brew install slp/krun/libkrun— third-party tap; required by minvmd.- The
minimalshim onPATH(~/.minimal/shim/bin/minimal). On macOS,materializerunsminimalinside a Linux VM; a from-source macOS build cannot run the build pipeline (thesandboxcrate is Linux-only).
# 1. Materialize the kernel + GENERIC guest rootfs INTO THE REPO.
# macOS caveat: the shim runs minimal in a VM and only syncs the project dir
# back to the host, so --output MUST be a path under the repo. A /tmp path is
# written inside the VM and never appears on the host.
mkdir -p .scratch
minimal materialize --output .scratch/vmlinuz --arch aarch64 virtio-kernel
minimal materialize --output .scratch/rootfs.img --arch aarch64 minvmd-rootfs
# 2. Build the guest initramfs (cross-compiles minimald → static aarch64, cpio).
scripts/build-initramfs.sh .scratch/initramfs.cpio
# 3. Build minvmd WITHOUT running.
cargo build -p minvmd --bin minvmd
# 4. Codesign minvmd with the hypervisor entitlement. This MUST be the last
# thing to touch the binary: a later `cargo run`/`cargo test` relinks and
# unsigns it, and krun_start_enter then fails with EINVAL.
codesign --entitlements crates/minvmd/minvmd.entitlements --force -s - target/debug/minvmd
# 5. Boot. The kernel runs the initramfs /init (minimald) as pid-1; minimald
# mounts the rootfs (/dev/vda) + pseudo-fs, then writes READY over vsock.
export MINVMD_KERNEL_PATH="$PWD/.scratch/vmlinuz"
export
9DDF
MINVMD_ROOTFS_PATH="$PWD/.scratch/rootfs.img" # generic microvm-rootfs
export MINVMD_INITRAMFS="$PWD/.scratch/initramfs.cpio"
target/debug/minvmd boot --foreground # vm-up = minimald READY from the initramfsOn a Linux host with /dev/kvm, minvmd links libkrun's KVM backend and boots
natively — no Docker, no codesigning. Unlike macOS, materialize runs the
build pipeline directly, so a native host (aarch64 or x86_64) materializes its
own artifacts. This is the same flow as the ci-linux-kvm.yml lane.
Prereqs:
- A KVM-capable Linux host, and membership in the
kvmgroup (durable — see the note at the end). - A Rust toolchain plus
protoc,jq, andcpio. For the no-Docker initramfs build you also needmusl-gccand the<arch>-unknown-linux-musltarget.
ARCH drives every step — set it to the host arch:
ARCH=aarch64 # or x86_64
RUST_MUSL="${ARCH}-unknown-linux-musl"
KRUN_PREFIX="$HOME/.krun"
mkdir -p .scratch
# 1. libkrun (+ its libkrunfw firmware) from the upstream `libkrun` package. A
# CACHE FETCH keyed by the pinned upstream commit — NOT a from-source build
# (libkrunfw would otherwise compile a guest kernel). Extracts libkrun.so* +
# libkrunfw.so* into $KRUN_PREFIX, a flat link/runtime prefix.
./scripts/fetch-libkrun.sh "$KRUN_PREFIX" "$ARCH"
# 2. Guest kernel + generic rootfs (cache fetches of the upstream packages).
./scripts/fetch-artifact.sh virtio-kernel .scratch/vmlinuz "$ARCH"
./scripts/fetch-artifact.sh minvmd-rootfs .scratch/rootfs.img "$ARCH"
# 3. Initramfs (minimald cross-compiled to a static musl binary, packed as /init).
scripts/build-initramfs.sh .scratch/initramfs.cpio "$RUST_MUSL" # uses `cross` (Docker)
# 4. Build minvmd. LIBKRUN_PREFIX must point at the POPULATED prefix from step 1
# BEFORE this build: build.rs trusts the env var's presence to emit the real
# (libkrun-linking) cfg + an rpath to it. Build before step 1 finishes and you
# get the runtime-bailing stub.
export LIBKRUN_PREFIX="$KRUN_PREFIX"
export LD_LIBRARY_PATH="$KRUN_PREFIX${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
cargo build -p minvmd --bin minvmd
# 5. Boot. minvmd needs LD_LIBRARY_PATH at runtime too: libkrun dlopen()s
# libkrunfw.so.5 from the prefix (it is not a link-time DT_NEEDED).
export MINVMD_KERNEL_PATH="$PWD/.scratch/vmlinuz"
export MINVMD_ROOTFS_PATH="$PWD/.scratch/rootfs.img"
export MINVMD_INITRAMFS="$PWD/.scratch/initramfs.cpio"
export MINVMD_BOOT_LOG="$PWD/.scratch/boot.log" # guest hvc0 console
sg kvm -c "LD_LIBRARY_PATH=$KRUN_PREFIX ./target/debug/minvmd boot --foreground"
# vm-up on stdout = minimald READY from the initramfs (R2.4).No Docker? Replace step 3 with a native musl build of minimald, then pack the cpio yourself:
env CC_${ARCH}_unknown_linux_musl=musl-gcc \
"CARGO_TARGET_$(echo "$ARCH" | tr a-z A-Z)_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc" \
cargo build -p minimald --profile initramfs --target "$RUST_MUSL"
STAGE="$(mktemp -d)"
mkdir -p "$STAGE"/dev "$STAGE"/proc "$STAGE"/sys "$STAGE"/newroot
cp "target/$RUST_MUSL/initramfs/minimald" "$STAGE/init"
( cd "$STAGE" && find . | cpio -o -H newc ) > .scratch/initramfs.cpio
rm -rf "$STAGE"No codesigning on Linux, so cargo test works directly. With the artifacts from
steps 1–3 and the libkrun env from step 4 exported (mirrors the CI lane's test
steps):
sg kvm -c "env LD_LIBRARY_PATH=$KRUN_PREFIX \
MINVMD_E2E=1 \
MINVMD_KERNEL_PATH=$PWD/.scratch/vmlinuz \
MINVMD_ROOTFS_PATH=$PWD/.scratch/rootfs.img \
MINVMD_INITRAMFS=$PWD/.scratch/initramfs.cpio \
cargo test -p minvmd --test boot_integration -- --include-ignored --nocapture"
sg kvm -c "env LD_LIBRARY_PATH=$KRUN_PREFIX \
MINVMD_E2E=1 \
MINVMD_KERNEL_PATH=$PWD/.scratch/vmlinuz \
MINVMD_ROOTFS_PATH=$PWD/.scratch/rootfs.img \
MINVMD_INITRAMFS=$PWD/.scratch/initramfs.cpio \
cargo test -p minvmd --test minimald_session_integration \
-- --include-ignored --nocapture --exact minimald_exec_over_bridge"(Two invocations, mirroring the CI lane's separate steps. --exact is passed to
every selected --test binary, so combining both binaries under one
--exact minimald_exec_over_bridge would run zero tests in boot_integration. The
--exact filter on minimald_session_integration selects the bridge case; that binary
has other, non-bridge cases. Drop it to run all of them.)
/dev/kvm access: the device node is recreated on every VM teardown, which wipes
any setfacl ACL — so an ACL grant lasts a single boot. The durable fix is group
membership (survives node recreation); sg kvm -c '…' picks it up without a
re-login:
sudo usermod -aG kvm "$USER" # one-timeGotchas:
minvmd bootis the raw boot primitive and does not updateminvmd.toml, sominvmd status/stopreport "stopped"/"not running" even while the VM is up. The lifecycle (minvmd.toml) is the supervisor's job — useminvmd run --detach/status/stopto exercise it. Kill a straybootVM by PID (thebootparent + its hidden__krun-vmmchild).- Requires libkrun >= 1.19.0 (BLK=1 for
krun_add_disk2, plus the vsock TX-chain fix). The upstreamlibkrunpackage already pins 1.19.4.
MINVMD_E2E=1-gated, #[ignore] by default. On macOS, run the prebuilt test
binaries directly — cargo test after signing relinks and unsigns minvmd. (On
Linux there is no signing step; see "E2E tests (Linux)" above.)
cargo test -p minvmd --test boot_integration --test minimald_session_integration --no-run
codesign --entitlements crates/minvmd/minvmd.entitlements --force -s - target/debug/minvmd
# boot READY round-trip:
testbin="$(ls -1t target/debug/deps/boot_integration-* | grep -v '\.d$' | head -1)"
MINVMD_E2E=1 \
MINVMD_KERNEL_PATH="$PWD/.scratch/vmlinuz" \
MINVMD_ROOTFS_PATH="$PWD/.scratch/rootfs.img" \
MINVMD_INITRAMFS="$PWD/.scratch/initramfs.cpio" \
"$testbin" --include-ignored --nocapture
# full session (russh client → CreateSession → exec) over the bridge:
testbin="$(ls -1t target/debug/deps/minimald_session_integration-* | grep -v '\.d$' | head -1)"
MINVMD_E2E=1 \
MINVMD_KERNEL_PATH="$PWD/.scratch/vmlinuz" \
MINVMD_ROOTFS_PATH="$PWD/.scratch/rootfs.img" \
MINVMD_INITRAMFS="$PWD/.scratch/initramfs.cpio" \
"$testbin" --include-ignored --nocapture --exact minimald_exec_over_bridgescripts/bench-minvmd-boot.sh times minvmd boot to the guest READY marker over
N runs (default 10) and reports min/median/max. Needs a codesigned minvmd and
the kernel + rootfs + initramfs paths:
MINVMD_KERNEL_PATH="$PWD/.scratch/vmlinuz" \
MINVMD_ROOTFS_PATH="$PWD/.scratch/rootfs.img" \
MINVMD_INITRAMFS="$PWD/.scratch/initramfs.cpio" \
scripts/bench-minvmd-boot.shCI runs it (informational) in the boot-e2e job.
- Kernel loaded via
krun_set_kernelas a raw uncompressed aarch64Image(KernelFormat::Raw). Thevirtio-kerneloutput is built by the upstreamvirtio-kernel-rawpackage, which decompresses upstream'sImage.gzat build time. Loading raw skips libkrun's in-VMM gzip decompress (~77 ms, over half of boot-to-READY). - The kernel boots the initramfs (
krun_set_kernel's initramfs arg): it unpacks into a RAM root and runs/init(= minimald) as PID 1. cmdline is justconsole=hvc0 ipv6.disable=1— noroot=/init=(those are for a block root). - minimald-as-
/initmounts/dev(devtmpfs; the kernel does NOT auto-mount it for an initramfs root), mounts the rootfs (krun_add_disk2→/dev/vda) andchroots into it so/bin/sh+ libs resolve, then writesREADY\non vsock port 7350 (boot marker, R2.4). minvmdregisters the host UDS bridge viakrun_add_vsock_port2(.., listen = true); minimald serves SSH directly on that vsock port (run_on_vsock), so theminimalCLI's connection reaches the session with no in-guest relay. Requires libkrun >= 1.19.0 — 1.18.1's vsock device intermittently stalled a direct session (multi-descriptor TX-chain bug, fixed upstream); the prior workaround was a socat vsock→UDS relay.
- The guest rootfs is the generic upstream
microvm-rootfspackage (an ext4 image built frombase+socat) — minimald is delivered by the initramfs, so nothing is baked into the rootfs. - Session state and cache persist on a per-VM writable ext4 data volume
(
/dev/vdb) that minimald format-on-first-boot mounts at/var/lib/minimal(STATE_VOLUME_MOUNTPOINT) and relocates both state and cache onto. The/run/minimaltmpfs is no longer a fallback — guest session state is user data with no host copy. Per spec08-spec-vm-ext4-volume(shipped). - In CI (
.github/workflows/ci-macos.yml) the kernel + rootfs are materialized on a cheap Linux runner (scripts/fetch-artifact.sh— cache pulls of the upstream packages' prebuilt aarch64 artifacts) and the initramfs is cross-compiled there; all three are handed to the self-hosted boot job as artifacts.