QEMU microVM orchestrator. Define VMs, networks, and layers in a pontoon.yml file, build them with Docker, and launch them with KVM. The CLI mirrors docker compose where it makes sense.
- Linux with KVM (
/dev/kvm) - QEMU (
qemu-system-x86_64) - Docker (for building VM images and the kernel)
Install with uv:
uv tool install --editable .
pontoon doctor # check prerequisites
pontoon build-kernel # build the Linux kernel (one-time, cached)
pontoon build # build per-VM initrds from layers
sudo pontoon network up # create host bridges and TAPs
pontoon up # start all VMs
A runnable two-VM topology lives in examples/quickstart.
A pontoon.yml defines the topology. Pontoon searches upward from the working directory to find it, or accepts -f path/to/pontoon.yml.
version: "1"
defaults:
kernel: ./vm/build/vmlinuz
alpine: "3.21"
networks:
lan:
subnet: 10.0.0.0/24
wan:
subnet: 10.0.1.0/24
services:
router:
memory: 128m
cpus: 2
layers:
- base
- firewall
networks:
lan:
ipv4_address: 10.0.0.1
wan:
ipv4_address: 10.0.1.1
masquerade: true
kernel_args:
svc.start: firewall
webserver:
memory: 256m
cpus: 1
layers:
- base
- nginx
networks:
lan:
ipv4_address: 10.0.0.80
gateway: 10.0.0.1
kernel_args:
svc.start: nginxEach service is a QEMU microVM. Fields:
| Field | Description |
|---|---|
memory |
RAM allocation (64m, 128m, 256m, 1g) |
cpus |
vCPU count |
layers |
List of layer names composing the VM rootfs |
networks |
Network attachments with IP, gateway, masquerade |
kernel_args |
Extra kernel command line parameters |
Each network becomes a Linux bridge on the host. VMs attach via TAP interfaces. Set internal: true to prevent host-side routing.
Link networks connect exactly two VMs with a direct QEMU socket (no bridge):
networks:
wire:
link: trueLayers compose a VM's rootfs. Each layer is a directory with a layer.yml:
# layers/nginx/layer.yml
packages:
- nginx
configs:
- etc/nginx/nginx.conf
start: |
nginx -g 'daemon off;' &| Field | Description |
|---|---|
packages |
Alpine packages to install |
configs |
Files to copy from the layer directory into the image |
run |
Commands to execute during the Docker build |
start |
Shell script run at VM boot (as a background service) |
binary |
Host binary to inject (path/to/bin -> /dest/in/vm) |
Layers are resolved in order: local layers/ directory first, then the stdlib layers shipped with pontoon. Later layers override earlier ones for the same destination path.
Pontoon ships these layers:
| Layer | Contents |
|---|---|
base |
iproute2, iptables, iperf3, socat, tcpdump |
ssh |
OpenSSH server |
ssh-hardened |
SSH with restricted config |
redis |
Redis server |
memcached |
Memcached |
postgres |
PostgreSQL |
mariadb |
MariaDB |
prometheus |
Prometheus monitoring |
samba |
Samba file server |
squid |
Tinyproxy HTTP proxy |
vsftpd |
FTP server |
dante |
SOCKS proxy |
Commands follow docker compose conventions where applicable.
| Command | Description |
|---|---|
pontoon up |
Start all VMs. Detects and recreates VMs with changed initrds. |
pontoon down |
Stop all VMs and clean up state. |
pontoon restart [vm ...] |
Restart all or specific VMs. |
pontoon stop [vm ...] |
Stop VMs without removing state. |
pontoon start [vm ...] |
Start previously stopped VMs. |
pontoon kill [-s SIGNAL] [vm ...] |
Send a signal to VMs (default: SIGKILL). |
| Command | Description |
|---|---|
pontoon build [vm] |
Build per-VM initrds from layer definitions. |
pontoon build-kernel [--no-cache] |
Build the Linux kernel. Cached after first build. |
| Command | Description |
|---|---|
pontoon ps |
Show VM status (running, zombie, stopped). |
pontoon top [vm ...] |
Show processes running inside VMs. |
pontoon logs [vm] [--lines N] |
Stream VM console logs. All VMs if none specified. |
pontoon check [vm ...] |
Test serial connectivity to VMs. |
pontoon config |
Dump parsed configuration as JSON. |
pontoon doctor |
Check prerequisites, build state, and memory sizing. |
pontoon version |
Print pontoon version. |
| Command | Description |
|---|---|
pontoon exec VM COMMAND [-u USER] [--timeout N] |
Run a command inside a VM via serial. |
pontoon console VM |
Interactive serial console (Ctrl-] to exit). |
pontoon watch |
Open a tmux dashboard with all VM consoles. |
| Command | Description |
|---|---|
sudo pontoon network up |
Create host bridges and TAPs. |
sudo pontoon network down |
Remove host bridges and TAPs. |
pontoon network tcpdump NETWORK [FILTER...] |
Capture traffic on a bridge. |
pontoon mcp
Starts a Model Context Protocol server on stdio. This exposes VM control tools to AI agents (Claude Code, etc.) over JSON-RPC 2.0.
MCP tools:
| Tool | Description |
|---|---|
helo |
Server health check. Returns version and VM count. |
vm_helo |
Test connectivity to a specific VM. |
vm_exec |
Run a command inside a VM. |
vm_exec_bg |
Run a background command. Checks binary exists first. |
vm_tail |
Read a background command's log file. |
vm_cp |
Copy a host file into a VM via 9p share. |
vm_bulk_exec |
Run a command on multiple VMs in parallel. |
vm_ps |
List processes inside a VM. |
vm_pkill |
Signal processes by pattern inside a VM. |
vm_logs |
Read a VM's host-side console log. |
vm_console_stream |
Read raw serial output for N seconds. |
vm_restart |
Restart a single VM. |
network_tcpdump |
Capture traffic on a host bridge. |
topology_get |
Return the full parsed topology as JSON. |
range_up / range_down / range_status |
Lifecycle control. |
All VM-targeting tools accept an IP address or hostname.
MCP access is configured under the mcp key in pontoon.yml:
defaults:
mcp:
hide_tools: # remove from MCP tool listing
- topology_get
- range_status
allow_tools: # per-VM tool allowlist (default: unrestricted)
- vm_exec
- vm_exec_bg
- vm_tail
allow_binaries: # per-VM command allowlist for vm_exec
- myapp
deny_root: true # block user=root on vm_exec
services:
admin:
mcp:
allow_tools: ["*"] # override: full access
allow_binaries: ["*"]
deny_root: false| Setting | Scope | Semantics |
|---|---|---|
hide_tools |
global | Tools removed from the MCP listing entirely |
allow_tools |
per-VM + global default | Which MCP tools work on this VM. ["*"] = unrestricted, [] = none. |
allow_binaries |
per-VM + global default | Which commands vm_exec and vm_exec_bg allow. Matches the binary name (path-stripped). |
deny_root |
per-VM + global default | Block user: root on exec tools. |
Per-VM settings override the global default. Absent settings inherit from defaults.mcp.
Each VM runs as a QEMU microvm process with KVM acceleration. No BIOS, no PCI, no ACPI. Boot time is under 1 second for most VMs.
The rootfs is packed as a gzip-compressed cpio archive (initramfs). The kernel decompresses it into tmpfs at boot. An init.sh script (provided per-range) runs as PID 1, configures networking from kernel command line parameters, and starts services.
Communication with VMs uses two serial channels:
- ttyS0 (console): interactive shell, log capture
- hvc0 (MCP): dedicated channel for programmatic command execution
The MCP channel maintains persistent socket connections to avoid shell respawn overhead between commands.
pontoon doctor estimates boot memory requirements per VM:
doctor: memory
[ok] router: 128m ram, 6.4m initrd → ~43m peak, 85m free
[FAIL] agent: 64m ram, 9.5m initrd → ~53m peak, 11m free
→ increase memory to at least 69m
The peak includes the kernel (~22 MB), the compressed initrd in memory during decompression, and the decompressed rootfs in tmpfs. VMs need at least 15 MB of headroom above this peak to boot and run services.
| Docker Compose | Pontoon | Notes |
|---|---|---|
docker compose up |
pontoon up |
Auto-recreates VMs with changed initrds |
docker compose down |
pontoon down |
|
docker compose restart |
pontoon restart |
All or specific VMs |
docker compose stop |
pontoon stop |
|
docker compose start |
pontoon start |
|
docker compose kill |
pontoon kill |
|
docker compose ps |
pontoon ps |
Includes zombie detection |
docker compose top |
pontoon top |
|
docker compose logs |
pontoon logs |
|
docker compose exec |
pontoon exec |
Via serial, not docker exec |
docker compose build |
pontoon build |
Builds initrds, not images |
docker compose config |
pontoon config |
|
docker compose version |
pontoon version |
|
| — | pontoon doctor |
Prerequisite and memory checks |
| — | pontoon check |
Serial connectivity test |
| — | pontoon console |
Interactive serial console |
| — | pontoon watch |
tmux dashboard |
| — | pontoon mcp |
MCP server for AI agents |
| — | pontoon network up/down |
Host bridge management |
MIT