SampleSandboxMetrics previously filtered WHERE status IN ('running',
'starting', 'paused'), which returned no rows when all capsules were
stopped. This caused zero snapshots to be skipped, leaving the
time-series charts with no trailing data points instead of showing
the expected zero values.
Remove the WHERE filter so the query groups by all teams that have
any sandbox row. The per-status FILTER clauses on the aggregates
already produce correct zero counts for stopped capsules.
Also includes the per-VM RAM ceiling formula change (sum(ceil(each/2))
instead of ceil(sum/2)).
Wrenn Sandbox
MicroVM-based code execution platform. Firecracker VMs, not containers. Pool-based pricing, persistent sandboxes, Python/TS/Go SDKs.
Deployment
Prerequisites
- Linux host with
/dev/kvmaccess (bare metal or nested virt) - Firecracker binary at
/usr/local/bin/firecracker - PostgreSQL
- Go 1.25+
Build
make build # outputs to builds/
Produces three binaries: wrenn-cp (control plane), wrenn-agent (host agent), envd (guest agent).
Host setup
The host agent machine needs:
# Kernel for guest VMs
mkdir -p /var/lib/wrenn/kernels
# Place a vmlinux kernel at /var/lib/wrenn/kernels/vmlinux
# Rootfs images
mkdir -p /var/lib/wrenn/images
# Build or place .ext4 rootfs images (e.g., minimal.ext4)
# Sandbox working directory
mkdir -p /var/lib/wrenn/sandboxes
# Snapshots directory
mkdir -p /var/lib/wrenn/snapshots
# Enable IP forwarding
sysctl -w net.ipv4.ip_forward=1
Configure
Copy .env.example to .env and edit:
# Required
DATABASE_URL=postgres://wrenn:wrenn@localhost:5432/wrenn?sslmode=disable
# Control plane
CP_LISTEN_ADDR=:8000
CP_HOST_AGENT_ADDR=http://localhost:50051
# Host agent
AGENT_LISTEN_ADDR=:50051
AGENT_FILES_ROOTDIR=/var/lib/wrenn
Run
# Apply database migrations
make migrate-up
# Start control plane
./builds/wrenn-cp
Control plane listens on CP_LISTEN_ADDR (default :8000).
Host registration
Hosts must be registered with the control plane before they can serve sandboxes.
-
Create a host record (via API or dashboard):
# As an admin (JWT auth) curl -X POST http://localhost:8000/v1/hosts \ -H "Authorization: Bearer $JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"type": "regular"}'This returns a
registration_token(valid for 1 hour). -
Start the host agent with the registration token and its externally-reachable address:
sudo AGENT_CP_URL=http://cp-host:8000 \ ./builds/wrenn-agent \ --register <token-from-step-1> \ --address 10.0.1.5:50051On first startup the agent sends its specs (arch, CPU, memory, disk) to the control plane, receives a long-lived host JWT, and saves it to
$AGENT_FILES_ROOTDIR/host-token. -
Subsequent startups don't need
--register— the agent loads the saved JWT automatically:sudo AGENT_CP_URL=http://cp-host:8000 \ ./builds/wrenn-agent --address 10.0.1.5:50051 -
If registration fails (e.g., network error after token was consumed), regenerate a token:
curl -X POST http://localhost:8000/v1/hosts/$HOST_ID/token \ -H "Authorization: Bearer $JWT_TOKEN"Then restart the agent with the new token.
The agent sends heartbeats to the control plane every 30 seconds. Host agent listens on AGENT_LISTEN_ADDR (default :50051).
Rootfs images
envd must be baked into every rootfs image. After building:
make build-envd
bash scripts/update-debug-rootfs.sh /var/lib/wrenn/images/minimal.ext4
Development
make dev # Start PostgreSQL (Docker), run migrations, start control plane
make dev-agent # Start host agent (separate terminal, sudo)
make check # fmt + vet + lint + test
See CLAUDE.md for full architecture documentation.