Skip to main content

Test report by platform

Last full run: 10/20/2026, rc binary on top of libkrun + libkrunfw. The baseline measurements (boot, memory, image build, per-stack execution) are from 10/19/2026, on the same host. Every number on this page was measured; whatever was not tested is stated as not tested.

Run of 10/20/2026 (macOS 27, Apple M1 Pro)​

GateResult
task test20 packages, all green (unit tests with -race)
task test:cov95.2% total, domain 100%, guest 75% (ratchet at 75), Python SDK 100% in lines and branches
task test:e2e42 of 42 tests green, in two consecutive runs
task runbook146 checks, 0 failures
task guides9 folders, 175 checks, 0 failures
task test:targetsdarwin/arm64, linux/amd64 and linux/arm64 compile; windows/amd64 rejected with the reason

On Linux, Landlock + seccomp were tested with the process actually confined on Ubuntu 26.04, in a VM without KVM; a full microVM on Linux was not measured. For --nested, only the rejection was measured (this Mac is an M1); the path that turns it on was not measured.

Update on the same date: a full microVM was measured on x86_64 Linux with KVM (a DigitalOcean droplet, nested KVM), with the job under Landlock (per thread, ABI 4) + seccomp, and the Kubernetes runtime passed 19/19 on k3s and microk8s. Details in Linux with KVM and Kubernetes.

Summary by platform​

PlatformEnvironmentReal microVMResult
macOSMacBook M1 Pro, macOS 27.0.1, Hypervisor.frameworkyese2e 42/42 in two consecutive runs; runbook and guides with no failures
LinuxUbuntu 26.04.1 LTS on VMPal, kernel 7.0.0-30, aarch64no (no /dev/kvm)Landlock + seccomp confinement applied and proven on the real process; 30 tests green in two runs
Linux with KVMDigitalOcean droplet s-4vcpu-8gb, Ubuntu 24.04, kernel 6.8, x86_64, nested KVMyesPython job under Landlock (ABI 4, per thread) + seccomp; Kubernetes 19/19 on k3s 1.36 and microk8s 1.35, two runs
WindowsWindows 11 Pro 24H2 (build 26100.9457) ARM64 on VMPalnoout of scope: build rejected on purpose; the hypervisor does not start in the VM (EL2 not present)

The common reason for the "no" on Linux and Windows: both are VMs inside an M1 Pro. Virtualization.framework only provides the EL2 exception level (needed for a hypervisor inside the VM) starting with the M3. Without EL2, no nested VM starts, whether KVM or Hyper-V.

macOS (target measured end to end)​

Environment​

ItemValue
hardwareMacBook Pro M1 Pro (arm64)
systemmacOS 27.0.1
VMMlibkrun 1.19.6 (the repo's Nix derivation, nix/)
guest kernellibkrunfw 5.6.2 β†’ Linux 6.12.109 aarch64
binarybin/rc, ad-hoc signed with com.apple.security.hypervisor
guestbin/rc-guest-linux-arm64, static, 2.6 MB

Boot and resources (spike, 30 boots per measurement)​

Measured with a harness that spawns the VM process and times it until the first response to a job over vsock.

MeasurementValue
boot to the first Python response, read-only ext4 rootp50 260 ms, p95 266 ms (min. 254, max. 279)
boot to the first sh response, virtiofs rootp50 245 ms, p95 264 ms
full cycle spawn β†’ uname β†’ shutdown221–252 ms with a warm cache; 869 ms on the first one
footprint of an idle VM (512 MiB configured)44 MB (footprint); RSS of 172 MB includes shared clean pages
configured RAM 256 / 1024 / 2048 MiBboot 260 / 260 / 261 ms; RSS +40 MB in total
SIGKILL on the process groupno process survives (every measured boot)

Decision taken from this: root on a read-only ext4 disk. virtiofs was not meaningfully faster and exposes a host directory to the guest.

Network​

Configurationwget http://1.1.1.1nc 1.1.1.1 80
vsock without TSI (default for job VMs)Network unreachablerc=1
vsock with TSI (build VM only)connectsrc=0

Proof in both directions: the test uses an IP, because with a name the DNS failure would hide the difference.

Image builds (15 stacks, rc image build --all)​

StackOCI imageBuildext4
awkalpine:3.222.6 s45 MiB
bashbash:5.22.9 s57 MiB
calpine:3.22 + build-base19.8 s332 MiB
cppalpine:3.22 + build-base19.5 s332 MiB
gogolang:1.25-alpine35.3 s377 MiB
javaeclipse-temurin:21-jdk-alpine34.0 s478 MiB
jqalpine:3.22 + jq2.7 s47 MiB
nodejsnode:22-alpine14.1 s234 MiB
perlalpine:3.22 + perl8.4 s98 MiB
phpphp:8.3-cli-alpine9.4 s152 MiB
pythonpython:3.12-alpine8.2 s128 MiB
rubyruby:3.3-alpine8.2 s134 MiB
rustrust:1-alpine37.4 s1.2 GiB
shalpine:3.222.2 s45 MiB
sqlitealpine:3.22 + sqlite2.8 s49 MiB

mkfs.ext4 dominates the time for large images (27.8 s of Go's 35 s): the ext4 is written on the host via virtiofs. Pull and flattening of python:3.12-alpine: 2.7 s, 55 MB.

Per-stack execution (rc run, cold boot)​

StackExecutionTotal (with boot)
python31–44 ms~240 ms
nodejs128–148 ms353–371 ms
go247 ms with a warm cache (3,429 ms without it)510 ms
java (JDK 21.0.12)522 ms739 ms
c71–72 ms282 ms
sh2 ms199 ms

API with a pre-booted pool​

SituationFull HTTP request
1 stack with an image, pool of 226–43 ms
15 stacks, others still refilling the pool73–78 ms
15 stacks, all pools full49–175 ms
before the global boot ceiling (30 VMs booting at once)1.7–2.3 s (regression found and fixed)

e2e suite (task test:e2e, tag e2e)​

42 tests, run twice in a row and green both times. Each test locks in a measured behavior. The table below describes the ones that pin down the contract for jobs and machines; the rest are in the following table.

TestWhat it proves
TestStdoutAndExitCodecorrect output; the program's exit 3 becomes a non-zero rc from the CLI
TestTimeoutKillsVMan infinite loop times out in ~2.2 s and no VM process is left behind
TestNoNetworkconnection to 1.1.1.1:80 blocked. Proof of failure: with Network: true planted in the pool, the test failed (the Python probe printed that it connected; the message was in Portuguese at the time and is now guest reached the network: "CONNECTED\n"); restored, it passed
TestRootReadOnlyAndIsolation/etc does not accept writes; a file written to /tmp by one job does not exist in the next
TestMemoryCeilingallocating 2 GiB in a 512 MiB VM fails inside the VM without affecting the host
TestLanguageFamilies13 stacks run (nodejs, go, java, c, cpp, rust, ruby, php, perl, bash, awk, jq, sqlite); a failed Java compilation exits with a non-zero rc
TestShellrc shell with stdin from a pipe comes up with a read-only root, writable /tmp and no network; on the bash stack it is real bash 5.2
TestShellTTYrc shell under a real pseudo-terminal (creack/pty, 40Γ—120): the guest's stdin is a terminal, stty size returns 40 120, and Ctrl+C interrupts a sleep 30 without bringing down the VM (the test takes ~1.4 s; without the interrupt it would take 30 s). Proof of failure: without raw mode on the host the test failed (VM survived the shell exit); restored, it passed
TestServePoolserve fills the pools, handles /run and, on SIGTERM, leaves no VM alive
TestRunStreamsrc run with Python that writes, sleeps 1.5 s and writes again: the 1st line reaches the host's stdout β‰₯ 1 s before the 2nd (measured: 0.25 s and 1.75 s after start). Proof of failure: with rc run buffering the output, both lines arrived 3.8 Β΅s apart and the test failed
TestServeStreamPOST /api/v1/execute/stream with the real VM: text/event-stream, stdout events with the 1st line β‰₯ 1 s before the 2nd, a final result with exitCode 0; a rejected job responds 400 before the stream
TestMachineLifecyclerc machine: create, CONFLICT on a repeated name, INVALID_STATE on exec with the machine stopped, start (198–244 ms), writes to /etc, /opt and /workspace, exec rc passed through (7), the stack's Python running inside, stop, logs with the machine stopped, start again: everything persisted and /tmp came back empty; delete leaves no VM. Proof of failure: with the machine booting without machine.sh, the write to /etc failed and the test failed
TestMachineShellTTYrc machine shell under a real pseudo-terminal (40Γ—120) on a running machine: the shell sees a TTY and 40 120, Ctrl+C interrupts a sleep 30 without killing the shell, what the shell writes to /workspace is visible to exec, exit 4 becomes rc 4 and the machine stays running. Proof of failure: without devpts in machine.sh (the real defect found: open /dev/ptmx: no such device), the shell did not open and the test failed
TestRunnerCodemachine from a runner.code with --cpus 1 on top of the file (the flag wins); the /app workdir is created on start; the workload comes up with env and workdir and shows in logs --source workload; init runs once (counter at 1 after stop+start); stop sends SIGINT and the workload's trap writes before the sync. Proof of failure (two planted defects): stop sending SIGTERM instead of stop_signal β†’ the trap's file does not exist; init ignoring the marker β†’ counter at 2. Both failed
TestRunnerCodeStopOnExitwith stop_on_exit, the end of the workload shuts the machine down by itself (~0.5 s) and the log shows the output and exited with code 0
TestRunnerCodeRefusalsnet = true rejected with the reason and nothing created; neither stack nor image is an error
TestRunnerCodeImage--image alpine:3.20 (image outside the catalog): built, boots and runs cat /etc/alpine-release
TestMachineNoNetworka machine without --net has no eth0 and cannot get out
TestMachineNetworkwith --net: eth0 100.96.0.2/30, DNS on the gateway, example.com responds; metadata 169.254.169.254 rejected immediately even with the network open
TestMachineEgressPolicy--allow-host example.com: the name goes through, iana.org gives bad address in under 1 s and goes into egress-denials.log; machine update allows and removes while the machine is running. Proof of failure: with the gateway ignoring the policy, metadata and the blocked name went through and the test failed
TestMachinePortsAndVolumesrunner.code with rw and ro volumes, a published port and no --net: the workload serves the host file through the port, the guest's write shows up on the host, :ro rejects writes even after mount -o remount,rw by the guest's root, the guest cannot dial out, and the volumes come back on every start. Proof of failure: with :ro only in the mount option (no flag on the device), the remount opened the volume and the test failed. Before this check, that defect slipped through
TestMachineCopyrc machine cp of 5 MiB (several chunks) to the machine and back: identical sha256 inside and outside
TestMachineAPI/api/v1/machines routes: 201/409/200/204/404, exec with rc 4, exec/stream with the 1st line β‰₯ 1 s before the 2nd and exit at the end, identical PUT/GET of 2.6 MB, INVALID_STATE (409) with the machine stopped, stop in under 5 s. Proof of failure: with the VM detached without Wait (a zombie in rc serve), stop took 10 s and the test failed
TestGPUVulkan--gpu on the llama stack: vulkaninfo shows Virtio-GPU Venus (Apple M1 Pro); without --gpu there is no /dev/dri; --gpu on a stack without GPU is rejected with E007. Proof of failure: with Configure missing the krun_set_gpu_options2 call, the test failed (no Venus device with --gpu)
TestGPULlamallama-bench with the same model on the GPU (-ngl 99 -dev Vulkan0) and on the CPU: the device used is Venus and the prompt comes out β‰₯ 2Γ— faster (measured 893–1,365 t/s versus 193–264 t/s, 4 runs)

Tests added with stories 7.4 to 7.6 and with the guides:

TestWhat it proves
TestMachineRunmachine run: the command runs in a machine that disappears at the end, the rc is the command's, -i passes stdin through, nothing to run is an error before creating anything
TestMachineRunDetached-d leaves the machine running with the workload under the requested name
TestMachineRunTTY-it under a real pseudo-terminal opens the shell with a TTY; its exit becomes the rc and the machine disappears
TestMachineRunCallerKilleda machine run whose CLI gets SIGKILL does not leave the VM alive without an owner (before the fix, it did)
TestMachineBranchAndPausewhen paused, the machine rejects exec with the reason; the branch takes what the parent just wrote; parent and child stay independent; resume brings the parent back
TestMachineEgressCIDROnlywith only allow_cidrs, a resolved name does not open a connection (hole closed on 10/19/2026); the allowed CIDR stays reachable
TestMachinePingICMP goes through the gateway under the same policy as TCP; a destination outside the allowlist does not come back and is recorded in the denial log
TestNamedNetworkMachinestwo machines on the same network reach each other by IP and by <machine>.<network>.internal, without --net; another network cannot reach them; a stopped machine disappears from DNS
TestMachineCredentialthe guest only sees the placeholder and the HTTPS service receives the real value; a placeholder in the URL is rejected with 403; a host outside the binding receives the literal placeholder
TestSecretsAndSSHAgent--secret-env/--secret-file reach exec and are not kept in the registry; a missing variable fails the start; --ssh-agent lists the host key without the private key entering the VM
TestExposeAndMountSockets--expose-socket and --mount-socket, in both directions
TestDockerSocketAndDiskVolumedocker_socket responds in the machine directory; a disk volume is real ext4, outside the overlay, and persists
TestStagedVolume:staged: the host does not change until sync; sync mirrors; graceful stop syncs; the child of a branch returns nothing
TestMachineS3Volumes3:// volume against a real S3 server: reads, byte-by-byte writes (8 MiB), mv and rm, :ro, remount after stop/start, wrong credentials fail the start with the reason
TestMachineDisplaythe guest's frame reaches the host viewer with the right colors; key presses and clicks via POST /input reach the guest's evdev
TestPackstack as stub + payload; single-file machine read with the origin deleted; persistent mode; create --from and pack inspect; rejections
TestDevStdinLinks/dev/stdin, /dev/stdout, /dev/stderr and /dev/fd exist in the job VM and in the machine

rc shell​

CheckResult
python: python3, writable /tmp, read-only /etc, no networkok
go: go version with the image's PATHgo1.25.14 linux/arm64
bash: prompt and [[ ]]bash-5.2#, ok
under a pseudo-terminalguest stdin = /dev/console on top of hvc0; test -t 0 true; stty size = host terminal size
Ctrl+Cinterrupts the command in the guest; the VM stays alive

How it works: with a terminal on stdin, rc shell turns on tty in the spec (krun_disable_implicit_console + krun_add_virtio_console_default(0, 1, 2); with all three fds being TTYs, libkrun creates a single console port) and puts the host terminal in raw mode for the session, restoring it before exiting. With stdin from a pipe the behavior stays as before (implicit console, no TTY). The tty command still prints not a tty, because it cannot find the device name; test -t 0 and stty show that it is a terminal.

Load event: one run of the suite with -count=2 failed 6 of 9 tests with a load average of ~24 on the host (VMPal VMs at 88% CPU and nix-daemon at 79%); TestServePool took 40 s versus the normal 7–8 s. Run in isolation and in two subsequent runs, the same suites passed in full.

Numbers from the 10/20/2026 batch​

MeasurementValue
packpython image 134 MiB β†’ 31 MiB payload; machine with an installed package, single file: 46 MiB
S3 volume8 MiB written in the guest reach the bucket with the same md5 (local S3 server)
security defect foundallow_cidrs alone let traffic out to any name; fixed and locked in by TestMachineEgressCIDROnly

Linux (confinement tested; microVM not)​

Environment​

ItemValue
VM"Ubuntu VM 2" on VMPal, 4 vCPU, 7.9 GB
systemUbuntu 26.04.1 LTS, kernel 7.0.0-30-generic, aarch64
active LSMslockdown,capability,landlock,yama,apparmor,ima,evm
LandlockABI 8 (landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION))
/dev/kvmabsent (no nested EL2 on the M1 Pro)

Method​

The test binary of the internal/adapters/krun package is compiled on the Mac for linux/arm64 and run in the VM:

CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go test -c -cover -o krun.test ./internal/adapters/krun/
vmpal cp krun.test "Ubuntu VM 2:rc-sandbox/krun.test"
# in the VM:
./krun.test -test.v -test.count=1 -test.coverprofile=cover.out

The confinement test relaunches its own binary as a probe. The probe applies the real policy (ApplySandbox, the same function the vmm process calls) and tries each operation. The test process itself is never confined.

Confinement result (confined probe)​

OperationExpectedMeasured
read the root diskallowedallowed
create the VM's own socket (bind)allowedallowed
read a file outside the policydenieddenied (Landlock)
write next to the root diskdenieddenied (Landlock)
TCP connectdenieddenied (Landlock, network)
execve("/bin/true")denieddenied (seccomp, EPERM)
personality(0xffffffff)denieddenied (seccomp, EPERM)
connect to another VM's Unix socketdenied from ABI 9 onwardallowed (kernel with ABI 8)

Proof of failure (TestProbeWithoutSandbox): the same probe without confinement reads the forbidden file, writes next to the disk, connects to another VM's socket, opens TCP and calls personality. In other words, the test tells them apart.

ptrace is not used as seccomp proof: on Ubuntu, Yama already denies ptrace to the parent without any confinement.

Suite result on Linux​

30 tests, green in two consecutive runs (TestLoadRealLibrary skipped: no libkrun in the VM). Package coverage: 81.9%. Code executed inside the confined subprocess is not included in the measurement.

rc doctor in the VM​

FAIL  platform     no access to /dev/kvm: open /dev/kvm: no such file or directory
ok sandbox landlock ABI 8 + seccomp denylist
warn landlock ABI 8 < 9: a VM process can still connect to another VM's unix socket

Not tested on Linux​

  • a full microVM (boot, job, timeout) under Landlock + seccomp: it requires KVM;
  • the list of paths libkrun opens in krun_start_enter on Linux. The policy allows the loaded libraries, /proc, /sys, /dev/kvm and the common device nodes, and preloads libkrunfw. If libkrun needs another path, the VM does not boot; RUNNER_SANDBOX=off disables confinement for diagnostics;
  • linux/amd64 (compiled only).

Linux with KVM and Kubernetes​

Environment​

ItemValue
machineDigitalOcean droplet s-4vcpu-8gb (4 vCPU, 8 GB), nyc3; scripts/do-kvm-probe.sh measured /dev/kvm and the vmx flags before creating the test node
systemUbuntu 24.04, kernel 6.8.0-142, x86_64; Landlock ABI 4
libkrun1.19.5 and libkrunfw 5.5.0 from nixpkgs (task krun:install)
Kubernetesk3s v1.36.5 (containerd 2.3.4, flannel) and microk8s v1.35.6 (containerd 2.1.6, Calico), one droplet each

Result​

WhatMeasured
rc run --stack python, cold bootstatus=success, ~1.4 s end to end (nested KVM; ~240 ms on the Mac)
rc image build --stack python60 s (mkfs took 45 s)
runtimeClassName: rc pod to Running~2 s, image already on the node
deploy/k8s/e2e.sh19/19 on k3s and microk8s, two consecutive runs on each
e2e failure proofexit code fixed at 0 in the shim: the 2 exit-code checks failed
task k8s:defects20 defects planted; 19 caught on the 1st run, the one that slipped through became a test, 20 of 20 after
TestRestrictThreadBlocks (root, real kernel)per-thread Landlock blocks the outside file; without landlock_restrict_self the test fails
TestAttach (root, real netns)tap0 is created in the pod netns, not the host one, with one mirred filter each way

Defects found only on real Linux​

SymptomCauseFix
nix profile add .#krun fails with File existsin the nixpkgs libkrun, lib/ and lib64/ carry the same filesln -sf in flake.nix
every job fails: prctl(PR_SET_NO_NEW_PRIVS): operation not supportedbelow ABI 8 go-landlock uses psx, and without cgo AllThreadsSyscall returns ENOTSUP (purego makes the runtime see itself "with cgo")ruleset on the krun_start_enter thread (sandbox_rules.go); libkrun threads inherit it
launcher test did not compile on Linuxsyscall.Getsid only exists on BSD/macOSunix.Getsid
rootfs copy "fails" with waitid: no child processespkg/shim reaps every child on SIGCHLDshim children wait through reaper.Default
kubelet summary breaks for the whole node; namespace stuck in Terminatingempty Stats from the shim (failed to decode container metrics)cgroup v2 metrics with the real vmm usage
microk8s: unsupported protocol: Yunixcontainerd 2.1 read the protobuf start reply as an addresslegacy JSON for callers of the old protocol
microk8s: pod without network (Network is unreachable)Calico gives a /32 with gateway 169.254.1.1 outside the subnetlink route to the gateway before the default one

Not tested on Linux with KVM​

  • linux/arm64 with KVM (x86_64 only);
  • Landlock with ABI β‰₯ 8 (TSYNC path) with a real microVM: the droplet has ABI 4;
  • --nested turning on (the droplet's rc doctor says supported, without proof);
  • the pod VM's vmm runs without Landlock/seccomp (declared debt).

Windows (out of scope, measured)​

Environment​

ItemValue
VM"Windows VM 1" on VMPal
systemWindows 11 Pro 24H2, build 26100.9457, ARM64, CPU "Apple Silicon"

Building rc​

GOOS=windows GOARCH=arm64|amd64 go build ./cmd/rc
undefined: runnerCodesDoesNotSupportWindows_libkrunUpstreamHasNoWHPBackend

The rejection is deliberate: upstream libkrun only has KVM and Hypervisor.framework backends, with no Windows Hypervisor Platform.

Hypervisor inside the VM​

StepMeasured
initial stateHypervisorPresent=False; VirtualizationFirmwareEnabled, VMMonitorModeExtensions and SLAT all False; WSL not installed
vmpal set --nested onWindows: nested virtualization is for Linux VMs; Ubuntu: Nested virtualization needs a Mac with M3 or later
VMPal restore point antes-hypervisor-2026-10-19created
Enable-WindowsOptionalFeature HypervisorPlatform, VirtualMachinePlatform, Microsoft-Hyper-V-Allenabled, RestartNeeded=True
after rebootbcdedit hypervisorlaunchtype Auto; vmms and vmcompute running; HypervisorPresent=False
system logMicrosoft-Windows-Hyper-V-Hypervisor id=43: Hypervisor launch failed; EL2 not present.
reverted to the restore pointthe three features Disabled; vmms and vmcompute absent

Conclusion: without EL2 the VM cannot run any hypervisor, and no Windows setting works around that. Even with rc support, this VM would have nowhere to create microVMs.

What is missing to close the matrix​

GapWhat unblocks it
microVM under Landlock with TSYNC (ABI β‰₯ 8)Linux with KVM and Landlock ABI β‰₯ 8 (Ubuntu 26.04, kernel 7.0, has 8) (the droplet's ABI 4 was measured, through the per-thread path)
linux/arm64 running with KVMarm64 Linux with KVM (linux/amd64 was measured on the droplet)
Unix socket isolation between VMskernel with Landlock ABI β‰₯ 9
--nested actually turning onM3+ Mac or Linux with nestable KVM
native Windowsreplace upstream libkrun with the fork that has WHP (an architecture decision) and a Windows machine with virtualization