6.8 KiB
openshell-vm
Status: Experimental and work in progress (WIP). VM support is under active development and may change.
MicroVM runtime for OpenShell, powered by libkrun. Boots a lightweight ARM64 Linux VM on macOS (Apple Hypervisor.framework) or Linux (KVM) running a single-node k3s cluster with the OpenShell control plane.
Quick Start
# One-time setup: download pre-built runtime (~30s)
mise run vm:setup
# Build and run the VM
mise run vm
Prerequisites
- macOS (Apple Silicon) or Linux (aarch64 or x86_64 with KVM)
- Rust toolchain
- mise task runner
- Docker (for rootfs builds)
ghCLI (for downloading pre-built runtime)
macOS-Specific
The binary must be codesigned with the Hypervisor.framework entitlement. The mise run vm flow handles this automatically. To codesign manually:
codesign --entitlements crates/openshell-vm/entitlements.plist --force -s - target/debug/openshell-vm
Setup
Download Pre-Built Runtime (Default)
Downloads libkrun, libkrunfw, and gvproxy from the vm-dev GitHub Release:
mise run vm:setup
Build from Source
Compiles the runtime from source (15-45 minutes, needed for custom kernel work):
FROM_SOURCE=1 mise run vm:setup
On macOS this builds a custom libkrunfw (kernel firmware with bridge/netfilter support) via krunvm, then builds a portable libkrun. On Linux it builds both natively.
Build
Build the openshell-vm binary with embedded runtime:
mise run vm:build
This compresses runtime artifacts, compiles the Rust binary with include_bytes!() embedding, codesigns it (macOS), and stages the sidecar runtime bundle.
Rootfs
The rootfs is an Ubuntu filesystem containing k3s, pre-loaded container images, and the OpenShell binaries. Build it with:
# Base rootfs (~200-300MB, cold starts in ~30-60s)
mise run vm:rootfs -- --base
# Full rootfs (~2GB+, pre-initialized, boots in ~3-5s)
mise run vm:rootfs
Run
Default (Gateway Mode)
Boots the full OpenShell gateway -- k3s + openshell-server + openshell-sandbox:
mise run vm
Or run the binary directly:
./target/debug/openshell-vm
Custom Process
Run an arbitrary process inside a fresh VM instead of k3s:
./target/debug/openshell-vm --exec /bin/sh --vcpus 2 --mem 2048
Execute in a Running VM
Attach to a running VM and run a command:
./target/debug/openshell-vm exec -- ls /
./target/debug/openshell-vm exec -- sh # interactive shell
Named Instances
Run multiple isolated VM instances side-by-side:
./target/debug/openshell-vm --name dev
./target/debug/openshell-vm --name staging
Each instance gets its own extracted rootfs under ~/.local/share/openshell/openshell-vm/<version>/instances/<name>/rootfs.
CLI Reference
openshell-vm [OPTIONS] [COMMAND]
Options:
--rootfs <PATH> Path to aarch64 Linux rootfs directory
--name <NAME> Named VM instance (auto-clones rootfs)
--exec <PATH> Run a custom process instead of k3s
--args <ARGS>... Arguments to the executable
--env <KEY=VALUE>... Environment variables
--workdir <DIR> Working directory inside the VM [default: /]
-p, --port <H:G>... Port mappings (host_port:guest_port)
--vcpus <N> Virtual CPUs [default: 4 gateway, 2 exec]
--mem <MiB> RAM in MiB [default: 8192 gateway, 2048 exec]
--krun-log-level <0-5> libkrun log level [default: 1]
--net <BACKEND> Networking: gvproxy, tsi, none [default: gvproxy]
--reset Wipe runtime state before booting
Subcommands:
prepare-rootfs Ensure the target rootfs exists
exec Execute a command inside a running VM
mise Tasks Reference
| Task | Description |
|---|---|
vm |
Build and run the VM |
vm:build |
Build openshell-vm binary with embedded runtime |
vm:setup |
One-time setup: download (or build) the VM runtime |
vm:rootfs |
Build the VM rootfs tarball (-- --base for lightweight) |
vm:clean |
Remove all VM cached artifacts |
e2e:vm |
Boot VM and run smoke e2e tests |
Common Workflows
# First time setup
mise run vm:setup # download pre-built runtime (~30s)
mise run vm # build + run
# Day-to-day iteration
mise run vm # incremental build + run
# Need fresh rootfs
mise run vm:rootfs -- --base # rebuild base rootfs
mise run vm:build # rebuild binary with new rootfs
# Something broken, start over
mise run vm:clean # wipe everything
mise run vm:setup # re-download runtime
mise run vm # full rebuild + run
# Custom kernel work (rare)
FROM_SOURCE=1 mise run vm:setup
Architecture
Host (macOS / Linux)
openshell-vm binary
|-- Embedded runtime (libkrun, libkrunfw, gvproxy, rootfs.tar.zst)
|-- FFI: loads libkrun at runtime via dlopen
|-- gvproxy: virtio-net networking (real eth0 + DHCP)
|-- virtio-fs: shares rootfs with guest
\-- vsock: host-to-guest command execution (port 10777)
Guest VM (aarch64 Linux)
PID 1: openshell-vm-init.sh
|-- Mounts filesystems, configures networking
|-- Sets up bridge CNI, generates PKI
\-- Execs k3s server
|-- openshell-server (gateway control plane)
\-- openshell-sandbox (pod supervisor)
Environment Variables
| Variable | When | Purpose |
|---|---|---|
OPENSHELL_VM_RUNTIME_COMPRESSED_DIR |
Build time | Path to compressed runtime artifacts |
OPENSHELL_VM_RUNTIME_DIR |
Runtime | Override the runtime bundle directory |
OPENSHELL_VM_DIAG=1 |
Runtime | Enable diagnostic output inside the VM |
FROM_SOURCE=1 |
vm:setup |
Build runtime from source instead of downloading |
Custom Kernel (libkrunfw)
The stock libkrunfw (e.g. from Homebrew) lacks bridge, netfilter, and conntrack support needed for pod networking. OpenShell builds a custom libkrunfw with these enabled.
Build it via the setup command:
FROM_SOURCE=1 mise run vm:setup
See runtime/README.md for details on the kernel config and troubleshooting.
Testing
Integration tests require a built rootfs and macOS ARM64 with libkrun:
cargo test -p openshell-vm -- --ignored
Individual tests:
# Full gateway boot test (boots VM, waits for gRPC on port 30051)
cargo test -p openshell-vm gateway_boots -- --ignored
# Run a command inside the VM
cargo test -p openshell-vm gateway_exec_runs -- --ignored
# Exec into a running VM
cargo test -p openshell-vm gateway_exec_attaches -- --ignored
Verify kernel capabilities inside a running VM:
./target/debug/openshell-vm exec -- /srv/check-vm-capabilities.sh
./target/debug/openshell-vm exec -- /srv/check-vm-capabilities.sh --json