Self-Hosted on Bare Metal
Run Caged on your own servers for maximum control, data sovereignty, and cost efficiency at scale. This recipe walks through setting up a single bare-metal node running Firecracker VMs.
Prerequisites
- A dedicated server with:
- Linux (Ubuntu 22.04 recommended)
- KVM support (
/dev/kvm present)
- 32+ GB RAM (more = more concurrent sandboxes)
- SSD storage
- Docker and Docker Compose installed
- A domain with DNS access
Hardware Sizing
| Concurrent Sandboxes | RAM | CPU | Storage | Example Server |
|---|
| 10-20 | 64GB | 16c | 500GB SSD | Hetzner AX42 |
| 30-50 | 128GB | 32c | 1TB NVMe | Hetzner AX102 |
| 50-100 | 256GB | 64c | 2TB NVMe | Hetzner AX162 |
Each sandbox uses 128MB-8GB RAM + 1-8 vCPUs depending on configuration.
git clone https://github.com/caged-dev/caged.git
cd caged
# Copy the example environment file
cp .env.example .env
Edit .env:
# .env — Caged self-hosted configuration
# Domain (your server's domain)
CAGED_DOMAIN=caged.your-company.com
CAGED_API_URL=https://api.caged.your-company.com
# Database
POSTGRES_PASSWORD=your-secure-password-here
POSTGRES_DB=caged
# Redis
REDIS_PASSWORD=your-redis-password
# Object Storage (MinIO for self-hosted)
MINIO_ROOT_USER=caged
MINIO_ROOT_PASSWORD=your-minio-password
S3_BUCKET=caged-snapshots
# NATS
NATS_TOKEN=your-nats-token
# Secrets encryption key (generate with: openssl rand -hex 32)
ENCRYPTION_KEY=your-256-bit-hex-key
# Runtime mode
RUNTIME_MODE=firecracker # or "docker" for testing
# Admin credentials (first user)
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=your-admin-password
Step 2: Download Firecracker
# Download Firecracker binary
ARCH=$(uname -m)
FC_VERSION="1.7.0"
curl -L "https://github.com/firecracker-microvm/firecracker/releases/download/v${FC_VERSION}/firecracker-v${FC_VERSION}-${ARCH}.tgz" | tar xz
sudo mv release-v${FC_VERSION}-${ARCH}/firecracker-v${FC_VERSION}-${ARCH} /usr/local/bin/firecracker
sudo mv release-v${FC_VERSION}-${ARCH}/jailer-v${FC_VERSION}-${ARCH} /usr/local/bin/jailer
# Verify
firecracker --version
Step 3: Build VM Images
# Build the base rootfs images
cd images/
# Node.js 20 image
./build-rootfs.sh node-20
# Python 3.12 image
./build-rootfs.sh python-312
# Go 1.22 image
./build-rootfs.sh go-122
# Minimal Alpine image
./build-rootfs.sh minimal
This produces rootfs files in images/output/:
images/output/
├── node-20.ext4
├── python-312.ext4
├── go-122.ext4
├── minimal.ext4
└── vmlinux # Shared kernel
Step 4: Start the Stack
# Start all services
docker compose up -d
# Verify everything is running
docker compose ps
NAME STATUS PORTS
caged-server Up (healthy) 0.0.0.0:8080->8080/tcp
caged-postgres Up (healthy) 5432/tcp
caged-redis Up (healthy) 6379/tcp
caged-clickhouse Up (healthy) 8123/tcp
caged-minio Up (healthy) 9000/tcp, 9001/tcp
caged-nats Up (healthy) 4222/tcp
Use Caddy for automatic HTTPS:
# /etc/caddy/Caddyfile
api.caged.your-company.com {
reverse_proxy localhost:8080
}
*.preview.caged.your-company.com {
reverse_proxy localhost:8080
tls {
dns cloudflare {env.CF_API_TOKEN}
}
}
caged.your-company.com {
reverse_proxy localhost:3000 # Web dashboard
}
sudo systemctl restart caddy
Step 6: Verify Installation
# Check API health
curl https://api.caged.your-company.com/health
# Login with admin credentials
caged login
# API URL: https://api.caged.your-company.com
# API Key: (get from dashboard or API)
# Create a test sandbox
caged run --template minimal --cpus 1 --memory 128
# Connect and verify
caged connect cage-xxxxx
# You should get a shell inside a Firecracker VM
Step 7: Set Up KVM Permissions
# Ensure the caged-server user can access /dev/kvm
sudo usermod -aG kvm caged
sudo chmod 666 /dev/kvm
# Configure jailer directories
sudo mkdir -p /srv/jailer
sudo chown caged:caged /srv/jailer
Docker Mode (Testing Only)
For development/testing without Firecracker:
# In .env
RUNTIME_MODE=docker
# Start the stack
docker compose up -d
# Sandboxes will be Docker containers instead of VMs
# ⚠️ NOT suitable for production — weaker isolation
Updating
# Pull latest code
git pull origin main
# Rebuild and restart
docker compose build
docker compose up -d
# Run migrations
docker compose exec server /app/server migrate up
Monitoring
# Check sandbox resource usage
curl https://api.caged.your-company.com/admin/metrics \
-H "Authorization: Bearer admin_token"
# View logs
docker compose logs -f server
# ClickHouse events (all agent actions, file changes, etc.)
docker compose exec clickhouse clickhouse-client \
--query "SELECT * FROM events ORDER BY timestamp DESC LIMIT 10"
Tips
Start with Docker mode: Get the stack running with Docker first, then switch to Firecracker once you’ve verified everything works.
Overcommit RAM carefully: Firecracker VMs use balloon devices, but if all sandboxes use max memory simultaneously, you’ll OOM. Plan for 70% utilization.
Use NVMe storage: Firecracker VM boot time is heavily influenced by disk I/O. NVMe gives sub-200ms cold starts.
Backup PostgreSQL and MinIO: These hold all your state. Set up daily backups with pg_dump and MinIO’s mirror feature.
Never expose Firecracker’s API socket externally. The management socket should only be accessible by the Caged server process.