Skip to main content

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 SandboxesRAMCPUStorageExample Server
10-2064GB16c500GB SSDHetzner AX42
30-50128GB32c1TB NVMeHetzner AX102
50-100256GB64c2TB NVMeHetzner AX162
Each sandbox uses 128MB-8GB RAM + 1-8 vCPUs depending on configuration.

Step 1: Clone and Configure

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

Step 5: Configure Reverse Proxy

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.