This guide takes you from a fresh machine to a production-ready Containment Chamber signer. It links to detailed reference pages at each step so you can dive deeper without losing the thread.
If you only need a local development signer, follow the Quick Start instead. Quick Start is stateless and Web3Signer-compatible; this guide shows the state-backed path you need before relying on runtime auth policies, tokens, or chamber key generation.
Install
Section titled “Install”docker pull ghcr.io/unforeseen-consequences/containment-chamber:latestdocker run --rm ghcr.io/unforeseen-consequences/containment-chamber:latest --versioncurl -LO https://github.com/unforeseen-consequences/containment-chamber/releases/latest/download/containment-chamber-linux-amd64chmod +x containment-chamber-linux-amd64sudo mv containment-chamber-linux-amd64 /usr/local/bin/containment-chambercontainment-chamber --versionChoose one method and verify the binary or container image works.
See Installation for build-from-source, cargo install, and shell completions.
Choose a deployment model
Section titled “Choose a deployment model”| Model | Best For | Complexity | Docs |
|---|---|---|---|
| Docker Compose | Single-node production, fastest path | Low | Docker |
| Kubernetes | Multi-instance, automated rollouts | Medium | Kubernetes |
| Bare Metal + systemd | Max control, existing metal | Medium | Bare Metal |
| Nitro Enclave | AWS, hardware-rooted trust | High | Enclave |
This guide assumes Docker Compose for concreteness. Adapt the file paths and commands if you chose a different model.
Configure
Section titled “Configure”Create a production-ready config.yaml:
server: listen_address: "0.0.0.0" listen_port: 9000 # Restrict ceremony endpoints to your admin network ceremony_allowed_cidrs: - "10.0.0.0/8"
key_sources: filesystem: paths: - /keystores
signer_state: backend: dynamodb table: containment-state refresh_interval_seconds: 1
anti_slashing: backend: postgres url: "postgresql://cc:env:DB_PASSWORD@postgres:5432/slashing"
metrics: listen_address: "0.0.0.0" listen_port: 3000Create the DynamoDB state table before starting the signer, or omit signer_state and skip the auth/key-generation steps if you intentionally want a stateless Web3Signer-compatible deployment.
See Configuration Reference for every option.
Load keys
Section titled “Load keys”Place your EIP-2335 keystores (JSON files) in a keystores/ directory alongside your config.yaml. The directory layout is:
keystores/├── 0x8f1e...a2b3.json # keystore file├── 0x8f1e...a2b3.txt # password file (same basename)└── ...This filesystem setup loads existing keys; it does not enable chamber-side key generation. To use /api/v1/chamber/keys/generate, configure DynamoDB Key Source plus signer_state, then enable chamber.keys.generate.enabled.
Set up auth
Section titled “Set up auth”This step assumes the signer_state backend from the config above is available and the chamber has been initialized and unsealed. For a fresh state backend, run the Seal & Unseal Guide first. Then generate a management token and create a policy that restricts signing to specific validator keys:
# Generate the root management tokencontainment-chamber operator generate-root-token alice \ --signer-url http://localhost:9000 \ --allow-plaintext-signer
# Create a policy that allows signing only for your keyscontainment-chamber operator auth policy create \ --name validator-signing \ --rules '[{"effect":"allow","scopes":["sign","public_keys"],"keys":["0x8f1e...a2b3","0xabcd...1234"]}]' \ --token env:ROOT_TOKEN \ --signer-url http://localhost:9000 \ --allow-plaintext-signer
# Create a token bound to that policycontainment-chamber operator auth token create \ --policies validator-signing \ --ttl-seconds 86400 \ --token env:ROOT_TOKEN \ --signer-url http://localhost:9000 \ --allow-plaintext-signerSave the management token from generate-root-token as ROOT_TOKEN. Save the client token returned by auth token create for your validator client — token secrets are shown only once. In production, add --token-bound-cidrs <validator-egress-cidr> when the validator client’s source network is stable.
See Auth Policies & Tokens for the full model and additional scopes. For the request lifecycle and how policies are evaluated, see API Concepts.
Deploy
Section titled “Deploy”Start the signer and its dependencies:
docker compose up -dIf you used the Docker Compose example, this also starts PostgreSQL and creates the slashing-protection database automatically.
Verify
Section titled “Verify”Check health, loaded keys, and metrics:
# Livenesscurl http://localhost:9000/upcheck
# Loaded keyscurl -H "Authorization: Bearer $TOKEN" \ http://localhost:9000/api/v1/eth2/publicKeys
# Prometheus metricscurl http://localhost:3000/metricsConnect your validator client. For Lighthouse:
lighthouse vc \ --beacon-node http://localhost:5052 \ --web3-signer-url http://localhost:9000 \ --web3-signer-token $TOKENSee Validator Clients for Teku, Prysm, Lodestar, and Nimbus examples.
Harden
Section titled “Harden”Before you point real validators at this signer, complete the production hardening checklist:
- Enable ceremony CIDR allowlists (already in the config above)
- Set up TLS termination or aTLS for enclave deployments
- Configure log forwarding and alerting on signing error rates
- Review the security model and threat assumptions
- Set up automated backups for the anti-slashing database
See Production Hardening for the complete checklist.
Next Steps
Section titled “Next Steps”- Monitor: Set up Prometheus/Grafana dashboards using the metrics on port 3000 — see Observability
- Scale: Run multiple instances behind a load balancer with the PostgreSQL anti-slashing backend
- Rotate keys: Use the Seal & Unseal Guide for operator ceremonies and key rotation
- Upgrade: Follow the Upgrading guide for safe version bumps with zero downtime