Containment Chamber publishes Docker images to GitHub Container Registry. The runtime image uses a scratch base: no shell or package manager, just the statically-linked binary and CA certificates.
Quick Run
Section titled “Quick Run”Start a signer with a local config file and keystores directory:
docker run -d \ --cap-add IPC_LOCK \ -p 9000:9000 \ -p 3000:3000 \ -v ./config.yaml:/config.yaml:ro \ -v ./keystores:/keystores:ro \ ghcr.io/unforeseen-consequences/containment-chamber:latest \ server -c /config.yamlPort 9000 serves the signing API, and port 3000 exposes Prometheus metrics.
--cap-add IPC_LOCK lets the process call mlockall() so key material is not paged to swap. Without it, the signer still starts, but logs a warning that memory locking failed.
Docker Compose
Section titled “Docker Compose”Create a docker-compose.yml alongside your config.yaml and keystores/ directory:
services: containment-chamber: image: ghcr.io/unforeseen-consequences/containment-chamber:latest command: server -c /config.yaml ports: - "9000:9000" # signing API - "3000:3000" # metrics cap_add: - IPC_LOCK volumes: - ./config.yaml:/config.yaml:ro - ./keystores:/keystores:ro environment: CONTAINMENT_ANTI_SLASHING__URL: postgresql://cc:secret@postgres:5432/slashing restart: unless-stopped depends_on: postgres: condition: service_healthy
postgres: image: postgres:16-alpine environment: POSTGRES_USER: cc POSTGRES_PASSWORD: secret POSTGRES_DB: slashing volumes: - postgres-data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U cc -d slashing"] interval: 5s timeout: 3s retries: 5
volumes: postgres-data:Configuration
Section titled “Configuration”Create a minimal config.yaml that works with the Compose setup above:
server: listen_address: "0.0.0.0" listen_port: 9000
metrics: listen_address: "0.0.0.0" listen_port: 3000
network: mainnet
key_sources: filesystem: paths: - /keystores
anti_slashing: backend: postgres # URL is injected via CONTAINMENT_ANTI_SLASHING__URL environment variableThe CONTAINMENT_ANTI_SLASHING__URL environment variable in the Compose file overrides the config file value, keeping the database credentials out of the config.
Environment Variables
Section titled “Environment Variables”All configuration options can be set via environment variables using the CONTAINMENT_ prefix. Use double underscores (__) to represent nesting in the YAML structure.
CONTAINMENT_SERVER__LISTEN_PORT=9001CONTAINMENT_ANTI_SLASHING__BACKEND=postgresCONTAINMENT_ANTI_SLASHING__URL="postgresql://cc:secret@postgres:5432/slashing"Operations
Section titled “Operations”# Start all servicesdocker compose up -d
# Watch signer logsdocker compose logs -f containment-chamber
# Check healthcurl http://localhost:9000/upcheck
# List loaded validator keyscurl http://localhost:9000/api/v1/eth2/publicKeys
# Stop all servicesdocker compose down
# Stop and remove volumes (deletes slashing protection database)docker compose down -vImage Tags
Section titled “Image Tags”| Tag | Description |
|---|---|
latest |
Latest stable release |
vX.Y.Z |
Pinned to a specific version |
For production, pin to a specific version tag to avoid unexpected upgrades:
docker pull ghcr.io/unforeseen-consequences/containment-chamber:v1.0.0Multi-Architecture
Section titled “Multi-Architecture”Images are built for both linux/amd64 and linux/arm64. Docker automatically pulls the correct architecture for your platform — this includes Apple Silicon Macs, AWS Graviton instances, and standard x86 servers.
Next Steps
Section titled “Next Steps”- Kubernetes — scale to multiple replicas with Helm
- Production Hardening — lock down the container and network
- Observability — set up Prometheus metrics and log aggregation

