This page documents runtime configuration for containment-chamber server.
Server options can be supplied through a YAML config file, CONTAINMENT_ environment variables, or matching containment-chamber server CLI flags where a flag exists. Operator workflows use containment-chamber operator ... commands and are documented separately in the CLI Reference; they are not part of the server configuration file.
Example Configuration File
Section titled “Example Configuration File”A fully commented example configuration file is available at config.example.yaml. Copy it and customize for your deployment:
curl -O https://raw.githubusercontent.com/unforeseen-consequences/containment-chamber/main/config.example.yamlcp config.example.yaml config.yaml# Edit config.yaml to match your setupcontainment-chamber server -c config.yamlFull config.example.yaml
# Copy this file to config.yaml and customize for your deployment.## Usage:# containment-chamber server -c config.yaml## CLI flags override values from this file.# Environment variables with CONTAINMENT_ prefix also override (use __ for nesting).# Example: CONTAINMENT_ANTI_SLASHING__URL="postgresql://..." (env var names are automatically lowercased)
# Server settingsserver: listen_address: "0.0.0.0" # CLI: --server-listen-address listen_port: 9000 # CLI: --server-listen-port
# Metrics endpointmetrics: listen_address: "0.0.0.0" # CLI: --metrics-listen-address listen_port: 3000 # CLI: --metrics-listen-port refresh_interval_seconds: 30 # CLI: --metrics-refresh-interval-seconds
# Ethereum signing configuration# Network name used for genesis-validators-root validation. Signing remains fork-agnostic.# CLI: --networknetwork: mainnet
# Key sources: filesystem, DynamoDB, or bothkey_sources: filesystem: paths: - ./keystores/raw - ./keystores/pbkdf2 - ./keystores/scrypt keystore_load_concurrency: 128 raw_load_concurrency: 128 # DynamoDB key source (optional, requires AWS credentials) # dynamodb: # table: containment-keys # refresh_interval_seconds: 1
# Custody ceremony (non-Nitro): the KMS key set, threshold, and root-token recipients the master# key is Shamir-split across. A Nitro build bakes this into its PCR0-measured static and rejects# this block. See the Seal & Unseal guide.# ceremony:# generation: 1# kms_threshold: 2# kms_keys:# - arns: ["arn:aws:kms:us-east-1:123456789012:key/aaaaaaaa-1111-..."]# - arns: ["arn:aws:kms:us-east-1:123456789012:key/bbbbbbbb-2222-..."]# - arns: ["arn:aws:kms:us-east-1:123456789012:key/cccccccc-3333-..."]# root_token_recipients:# - "age1qz..."
# Signer state is required when key_sources.dynamodb is configured.# signer_state:# backend: dynamodb# table: containment-state# refresh_interval_seconds: 1
# Signing concurrency and priority queuessigning: max_concurrent_jobs: 2000 queue_buffer_size: 4000 # priority: # enabled: true # max_concurrent_jobs: 50 # operations: [BLOCK_V2]
# Anti-slashing backend# postgres is recommended for production (multi-instance safe)anti_slashing: backend: postgres url: "postgresql://user:password@localhost:5432/slashing?sslmode=require" pool_size: 64 # Force DNS resolution to IPv4 only. Enable for musl/scratch Docker images # with IPv6 routing issues (e.g., NAT64 on Kubernetes). Default: false. force_ipv4: false # TLS is enabled by default. Add ?sslmode=disable to the URL to disable. # AWS RDS CA is baked into the Docker image.# Alternative backends:# anti_slashing:# backend: sqlite# path: ./slashing_protection.sqlite# anti_slashing:# backend: noop # WARNING: no slashing protection
# ──────────────────────────────────────────────────────────────# Key Manager API — hot-load/remove EIP-2335 keystores at runtime# Disabled by default.# ──────────────────────────────────────────────────────────────http: key_manager_api: enabled: false # max_concurrent_reads: 50 # max_concurrent_writes: 10## To enable:# http:# key_manager_api:# enabled: true## State-backed auth (policies + tokens) is managed via `operator auth`, not the config file.# See the Signing Auth guide for details: /chamber-api/auth/## Stateless deployments declare auth in config via static_auth (rejected when signer_state is set):## static_auth:# policies:# - name: attester# rules:# - effect: allow# scopes: [sign, public_keys]# anonymous: # named policies applied to token-less requests (optional)# policies: [attester]# tokens: # static client tokens (optional)# - secret: env:CC_ATTESTER_TOKEN # "env:VAR" injects from env, or a clear-text value# policies: [attester]# bound_cidrs: ["10.0.0.0/8"] # optional: restrict this token to source CIDRs
# OpenTelemetry OTLP tracing export# opentelemetry:# enabled: false# endpoint: "http://localhost:4317"# service_name: "containment-chamber"
# Logging# logging:# level: "info"# format: "text"# color: nullConfiguration Layering
Section titled “Configuration Layering”Server configuration values are resolved in the following precedence order (highest wins):
- Server CLI flags — e.g.,
containment-chamber server --server-listen-port 9001 - Environment variables — e.g.,
CONTAINMENT_SERVER__LISTEN_PORT=9001 - Config file — e.g.,
config.yaml - Built-in defaults
To start with a config file:
containment-chamber server -c config.yamlOr use CLI flags directly:
containment-chamber server \ --key-sources-filesystem-paths ./keystores/raw \ --key-sources-filesystem-paths ./keystores/pbkdf2 \ --anti-slashing-backend sqlite \ --anti-slashing-sqlite-path ./slashing.sqliteEnvironment Variables
Section titled “Environment Variables”All configuration options can be set via environment variables using the CONTAINMENT_ prefix. Nested keys use __ (double underscore) as the separator.
# server.listen_port → CONTAINMENT_SERVER__LISTEN_PORTCONTAINMENT_SERVER__LISTEN_PORT=9001
# server.listen_address → CONTAINMENT_SERVER__LISTEN_ADDRESSCONTAINMENT_SERVER__LISTEN_ADDRESS="127.0.0.1"
# anti_slashing.url → CONTAINMENT_ANTI_SLASHING__URLCONTAINMENT_ANTI_SLASHING__URL="postgresql://user:pass@db/slashing"
# anti_slashing.backend → CONTAINMENT_ANTI_SLASHING__BACKENDCONTAINMENT_ANTI_SLASHING__BACKEND=postgres
# network → CONTAINMENT_NETWORKCONTAINMENT_NETWORK=mainnetServer Configuration
Section titled “Server Configuration”HTTP server settings for the signing API.
server: listen_address: "0.0.0.0" listen_port: 9000 seccomp: false # Enable seccomp syscall filter (Linux only). Default: falseServer
Section titled “Server”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
server.listen_address |
string | "0.0.0.0" |
--server-listen-address |
CONTAINMENT_SERVER__LISTEN_ADDRESS |
Bind address for the HTTP signing server. |
server.listen_port |
integer | 9000 |
--server-listen-port |
CONTAINMENT_SERVER__LISTEN_PORT |
Port for the HTTP signing server. |
server.request_timeout_seconds |
integer | 30 |
--server-request-timeout-seconds |
CONTAINMENT_SERVER__REQUEST_TIMEOUT_SECONDS |
Per-request timeout for signing HTTP handlers. |
server.graceful_shutdown_timeout_seconds |
integer | 25 |
--server-graceful-shutdown-timeout-seconds |
CONTAINMENT_SERVER__GRACEFUL_SHUTDOWN_TIMEOUT_SECONDS |
Graceful shutdown timeout before force-closing requests. |
server.cors_allowed_origins |
list of strings, or null | null |
--server-cors-allowed-origins |
CONTAINMENT_SERVER__CORS_ALLOWED_ORIGINS |
CORS allowed origins. Omit or null to disable. Use ["*"] to allow all origins. |
server.max_request_body_bytes |
integer | 2097152 |
— | CONTAINMENT_SERVER__MAX_REQUEST_BODY_BYTES |
Maximum request body size in bytes (2 MiB default). Requests exceeding this limit are rejected with 413. |
server.seccomp |
string (off, best_effort, required) | off |
— | CONTAINMENT_SERVER__SECCOMP |
Seccomp syscall-filter policy on Linux. required aborts boot if the filter cannot be installed. |
server.mlock |
string (off, best_effort, required) | best_effort |
— | CONTAINMENT_SERVER__MLOCK |
Memory-locking (mlockall) policy on Linux. required aborts boot if pages cannot be locked. Skipped automatically inside a TEE enclave. |
server.trust_proxy_headers |
boolean | false |
— | CONTAINMENT_SERVER__TRUST_PROXY_HEADERS |
Consult X-Forwarded-For for the client IP when the TCP peer is in trusted_proxy_cidrs. Default uses the peer IP verbatim. |
server.trusted_proxy_cidrs |
list of strings | [] |
— | CONTAINMENT_SERVER__TRUSTED_PROXY_CIDRS |
CIDR ranges whose peers may set X-Forwarded-For. Only consulted when trust_proxy_headers is true. Unsafe CIDRs are rejected at startup. |
server.health_check.poll_interval_seconds |
integer | 30 |
--server-health-check-poll-interval-seconds |
CONTAINMENT_SERVER__HEALTH_CHECK__POLL_INTERVAL_SECONDS |
Interval between background polls of the anti-slashing backend for the /healthcheck readiness probe. |
server.health_check.poll_timeout_ms |
integer | 2000 |
--server-health-check-poll-timeout-ms |
CONTAINMENT_SERVER__HEALTH_CHECK__POLL_TIMEOUT_MS |
Per-poll timeout budget (milliseconds) for the /healthcheck anti-slashing backend probe. |
Metrics
Section titled “Metrics”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
metrics.listen_address |
string | "0.0.0.0" |
--metrics-listen-address |
CONTAINMENT_METRICS__LISTEN_ADDRESS |
Bind address for the Prometheus metrics server. |
metrics.listen_port |
integer | 3000 |
--metrics-listen-port |
CONTAINMENT_METRICS__LISTEN_PORT |
Port for the Prometheus metrics endpoint. |
metrics.refresh_interval_seconds |
integer | 30 |
--metrics-refresh-interval-seconds |
CONTAINMENT_METRICS__REFRESH_INTERVAL_SECONDS |
How often metrics are refreshed. |
Network
Section titled “Network”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
network |
string | "mainnet" |
--network |
CONTAINMENT_NETWORK |
Ethereum network name (mainnet, hoodi, sepolia). |
Key Sources — Filesystem
Section titled “Key Sources — Filesystem”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
key_sources.filesystem.paths |
list of strings | — | --key-sources-filesystem-paths |
CONTAINMENT_KEY_SOURCES__FILESYSTEM__PATHS |
Directories containing keystore configuration files. |
key_sources.filesystem.keystore_load_concurrency |
integer | 128 |
--key-sources-filesystem-keystore-load-concurrency |
CONTAINMENT_KEY_SOURCES__FILESYSTEM__KEYSTORE_LOAD_CONCURRENCY |
Parallel encrypted keystore decryption workers. |
key_sources.filesystem.raw_load_concurrency |
integer | 128 |
--key-sources-filesystem-raw-load-concurrency |
CONTAINMENT_KEY_SOURCES__FILESYSTEM__RAW_LOAD_CONCURRENCY |
Parallel raw key loading workers. |
Key Sources — DynamoDB + KMS
Section titled “Key Sources — DynamoDB + KMS”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
key_sources.dynamodb.table |
string | — | --key-sources-dynamodb-table |
CONTAINMENT_KEY_SOURCES__DYNAMODB__TABLE |
DynamoDB table name for validator keys. |
key_sources.dynamodb.status_filter |
list of strings | ["active"] |
--key-sources-dynamodb-status-filter |
CONTAINMENT_KEY_SOURCES__DYNAMODB__STATUS_FILTER |
Validator statuses to load from DynamoDB. |
key_sources.dynamodb.refresh_interval_seconds |
integer | 1 |
--key-sources-dynamodb-refresh-interval-seconds |
CONTAINMENT_KEY_SOURCES__DYNAMODB__REFRESH_INTERVAL_SECONDS |
How often to refresh keys from DynamoDB (0 disables). |
key_sources.dynamodb.max_concurrent_reads |
integer | 16 |
--key-sources-dynamodb-max-concurrent-reads |
CONTAINMENT_KEY_SOURCES__DYNAMODB__MAX_CONCURRENT_READS |
Parallel DynamoDB read workers for key loading. |
Signer State
Section titled “Signer State”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
signer_state.backend |
enum | — | --signer-state-backend |
CONTAINMENT_SIGNER_STATE__BACKEND |
Signer-state backend. Currently supports dynamodb. |
signer_state.table |
string | — | --signer-state-dynamodb-table |
CONTAINMENT_SIGNER_STATE__TABLE |
DynamoDB table name for signer state (MASTER_KEY row, SEAL_OVERRIDE latch, and auth rows). Required when key_sources.dynamodb is configured. |
signer_state.refresh_interval_seconds |
integer | 1 |
--signer-state-refresh-interval-seconds |
CONTAINMENT_SIGNER_STATE__REFRESH_INTERVAL_SECONDS |
How often each instance checks state rows and reloads auth policies/tokens. Set to 0 to disable both state and auth refresh. |
Concurrency
Section titled “Concurrency”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
signing.max_concurrent_jobs |
integer | 2000 |
--signing-max-concurrent-jobs |
CONTAINMENT_SIGNING__MAX_CONCURRENT_JOBS |
Maximum concurrent signing operations. |
signing.queue_buffer_size |
integer | 4000 |
--signing-queue-buffer-size |
CONTAINMENT_SIGNING__QUEUE_BUFFER_SIZE |
Queue buffer size for incoming signing requests. |
signing.priority.enabled |
boolean | false |
— | CONTAINMENT_SIGNING__PRIORITY__ENABLED |
Enable priority signing pool for high-priority operations. |
signing.priority.max_concurrent_jobs |
integer | 50 |
— | CONTAINMENT_SIGNING__PRIORITY__MAX_CONCURRENT_JOBS |
Permits reserved for the priority signing pool (carved from signing.max_concurrent_jobs). |
signing.priority.operations |
list of strings | [] |
— | CONTAINMENT_SIGNING__PRIORITY__OPERATIONS |
List of operation types to route to priority pool (e.g., BLOCK_V2). |
Anti-Slashing
Section titled “Anti-Slashing”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
anti_slashing.backend |
enum | "sqlite" |
--anti-slashing-backend |
CONTAINMENT_ANTI_SLASHING__BACKEND |
Anti-slashing backend (noop, sqlite, postgres, dynamodb). |
anti_slashing.path |
string | "./slashing_protection.sqlite" |
--anti-slashing-sqlite-path |
CONTAINMENT_ANTI_SLASHING__PATH |
SQLite database path (sqlite backend only). |
anti_slashing.url |
string | — | --anti-slashing-postgres-url |
CONTAINMENT_ANTI_SLASHING__URL |
PostgreSQL connection URL (postgres backend only). |
anti_slashing.pool_size |
integer | 64 |
--anti-slashing-postgres-pool-size |
CONTAINMENT_ANTI_SLASHING__POOL_SIZE |
PostgreSQL connection pool size. |
anti_slashing.force_ipv4 |
boolean | false |
--anti-slashing-postgres-force-ipv4 |
CONTAINMENT_ANTI_SLASHING__FORCE_IPV4 |
Force PostgreSQL DNS resolution to IPv4 only. |
anti_slashing.table |
string | — | --anti-slashing-dynamodb-table |
CONTAINMENT_ANTI_SLASHING__TABLE |
DynamoDB table name (dynamodb backend only). |
anti_slashing.max_concurrent_writes |
integer | 256 |
--anti-slashing-dynamodb-max-concurrent-writes |
CONTAINMENT_ANTI_SLASHING__MAX_CONCURRENT_WRITES |
Application-level cap on concurrent DynamoDB anti-slash writes (dynamodb backend only). Sized at ~1/8 of signing.max_concurrent_jobs; raise it proportionally when increasing signing concurrency. |
Key Manager API
Section titled “Key Manager API”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
http.key_manager_api.enabled |
boolean | false |
--http-key-manager-api-enabled |
CONTAINMENT_HTTP__KEY_MANAGER_API__ENABLED |
Enable the Key Manager API. |
http.key_manager_api.max_concurrent_reads |
integer | 50 |
--http-key-manager-api-max-concurrent-reads |
CONTAINMENT_HTTP__KEY_MANAGER_API__MAX_CONCURRENT_READS |
Maximum concurrent Key Manager API reads (GET/DELETE). |
http.key_manager_api.max_concurrent_writes |
integer | 10 |
--http-key-manager-api-max-concurrent-writes |
CONTAINMENT_HTTP__KEY_MANAGER_API__MAX_CONCURRENT_WRITES |
Maximum concurrent Key Manager API writes (POST). |
http.key_manager_api.request_timeout_seconds |
integer | 30 |
--http-key-manager-api-timeout-seconds |
CONTAINMENT_HTTP__KEY_MANAGER_API__REQUEST_TIMEOUT_SECONDS |
Key Manager API request timeout in seconds. |
http.key_manager_api.max_items_per_request |
integer | 100 |
--http-key-manager-api-max-items-per-request |
CONTAINMENT_HTTP__KEY_MANAGER_API__MAX_ITEMS_PER_REQUEST |
Maximum items per Key Manager API import/delete request. |
Chamber Keys
Section titled “Chamber Keys”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
chamber.keys.list.max_concurrent_requests |
integer | 16 |
--chamber-keys-list-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__LIST__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap for GET /api/v1/chamber/keys. |
chamber.keys.generate.enabled |
boolean | false |
--chamber-keys-generate-enabled |
CONTAINMENT_CHAMBER__KEYS__GENERATE__ENABLED |
Mount POST /api/v1/chamber/keys/generate. Requires key_sources.dynamodb (enforced at boot). |
chamber.keys.generate.max_items_per_request |
integer | 100 |
--chamber-keys-generate-max-items-per-request |
CONTAINMENT_CHAMBER__KEYS__GENERATE__MAX_ITEMS_PER_REQUEST |
Maximum keys per keygen request. |
chamber.keys.generate.max_concurrent_requests |
integer | 16 |
--chamber-keys-generate-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__GENERATE__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap. Per-request batch parallelism is derived from std::thread::available_parallelism(). |
chamber.keys.generate.request_timeout_seconds |
integer | 600 |
--chamber-keys-generate-timeout-seconds |
CONTAINMENT_CHAMBER__KEYS__GENERATE__REQUEST_TIMEOUT_SECONDS |
Keygen request timeout in seconds. |
chamber.keys.generate_keys.enabled |
boolean | false |
--chamber-keys-generate-keys-enabled |
CONTAINMENT_CHAMBER__KEYS__GENERATE_KEYS__ENABLED |
Mount POST /api/v1/chamber/keys/generate-keys (keys only, no deposit data). Requires key_sources.dynamodb (enforced at boot). |
chamber.keys.generate_keys.max_items_per_request |
integer | 100 |
--chamber-keys-generate-keys-max-items-per-request |
CONTAINMENT_CHAMBER__KEYS__GENERATE_KEYS__MAX_ITEMS_PER_REQUEST |
Maximum keys per generate-keys request. |
chamber.keys.generate_keys.max_concurrent_requests |
integer | 16 |
--chamber-keys-generate-keys-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__GENERATE_KEYS__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap for the generate-keys route. Per-request batch parallelism is derived from std::thread::available_parallelism(). |
chamber.keys.generate_keys.request_timeout_seconds |
integer | 600 |
--chamber-keys-generate-keys-timeout-seconds |
CONTAINMENT_CHAMBER__KEYS__GENERATE_KEYS__REQUEST_TIMEOUT_SECONDS |
Generate-keys request timeout in seconds. |
chamber.keys.deposit_data.enabled |
boolean | false |
--chamber-keys-deposit-data-enabled |
CONTAINMENT_CHAMBER__KEYS__DEPOSIT_DATA__ENABLED |
Mount POST /api/v1/chamber/keys/deposit-data (deposit data for already-persisted keys). Requires key_sources.dynamodb (enforced at boot). |
chamber.keys.deposit_data.max_items_per_request |
integer | 100 |
--chamber-keys-deposit-data-max-items-per-request |
CONTAINMENT_CHAMBER__KEYS__DEPOSIT_DATA__MAX_ITEMS_PER_REQUEST |
Maximum pubkeys per deposit-data request. |
chamber.keys.deposit_data.max_concurrent_requests |
integer | 16 |
--chamber-keys-deposit-data-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__DEPOSIT_DATA__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap for the deposit-data route. Per-request batch parallelism is derived from std::thread::available_parallelism(). |
chamber.keys.deposit_data.request_timeout_seconds |
integer | 600 |
--chamber-keys-deposit-data-timeout-seconds |
CONTAINMENT_CHAMBER__KEYS__DEPOSIT_DATA__REQUEST_TIMEOUT_SECONDS |
Deposit-data request timeout in seconds. |
chamber.keys.deposit_data.allow_recompute |
boolean | false |
--chamber-keys-deposit-data-allow-recompute |
CONTAINMENT_CHAMBER__KEYS__DEPOSIT_DATA__ALLOW_RECOMPUTE |
Allow force_recompute_on_conflict=true in deposit-data requests. When false (default), such requests are rejected with 400 — an operator-level gate against overwriting existing deposit data. |
chamber.keys.backup.recipients |
list of strings | — | --chamber-keys-backup-recipients |
CONTAINMENT_CHAMBER__KEYS__BACKUP__RECIPIENTS |
age public key recipients for the BIP-39 mnemonic backup. A non-empty list enables backup; an absent or empty list disables it. |
chamber.keys.import.enabled |
boolean | false |
--chamber-keys-import-enabled |
CONTAINMENT_CHAMBER__KEYS__IMPORT__ENABLED |
Mount POST /api/v1/chamber/keys. Memory-only imports (storage.persist=false) work without a mutable backend. |
chamber.keys.import.max_items_per_request |
integer | 100 |
--chamber-keys-import-max-items-per-request |
CONTAINMENT_CHAMBER__KEYS__IMPORT__MAX_ITEMS_PER_REQUEST |
Maximum keys per import request. |
chamber.keys.import.max_concurrent_requests |
integer | 16 |
--chamber-keys-import-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__IMPORT__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap for chamber import. |
chamber.keys.import.request_timeout_seconds |
integer | 600 |
--chamber-keys-import-timeout-seconds |
CONTAINMENT_CHAMBER__KEYS__IMPORT__REQUEST_TIMEOUT_SECONDS |
Chamber import request timeout in seconds. |
chamber.keys.lifecycle.enabled |
boolean | false |
--chamber-keys-lifecycle-enabled |
CONTAINMENT_CHAMBER__KEYS__LIFECYCLE__ENABLED |
Mount PATCH+DELETE on /api/v1/chamber/keys. Per-key source-dispatched: memory keys can be deleted without a backend; persistent keys need one. |
chamber.keys.lifecycle.max_items_per_request |
integer | 500 |
--chamber-keys-lifecycle-max-items-per-request |
CONTAINMENT_CHAMBER__KEYS__LIFECYCLE__MAX_ITEMS_PER_REQUEST |
Maximum keys per PATCH/DELETE batch. |
chamber.keys.lifecycle.max_concurrent_requests |
integer | 16 |
--chamber-keys-lifecycle-max-concurrent-requests |
CONTAINMENT_CHAMBER__KEYS__LIFECYCLE__MAX_CONCURRENT_REQUESTS |
Tower concurrency cap for chamber lifecycle. |
chamber.keys.lifecycle.request_timeout_seconds |
integer | 600 |
--chamber-keys-lifecycle-timeout-seconds |
CONTAINMENT_CHAMBER__KEYS__LIFECYCLE__REQUEST_TIMEOUT_SECONDS |
Chamber lifecycle request timeout in seconds. |
Auth Policies
Section titled “Auth Policies”| Option | Type | Default | Env Var | Description |
|---|---|---|---|---|
static_auth |
object | — | CONTAINMENT_STATIC_AUTH |
Stateless-only static authorization: named policies, client tokens that reference them, and an optional anonymous binding. Rejected when signer_state is configured. |
static_auth.policies |
list of objects | [] |
CONTAINMENT_STATIC_AUTH__POLICIES |
Named policies: each has a name and rules (same PolicyRule schema as API policies — effect allow/deny, optional scopes, keys, operations). |
static_auth.anonymous.policies |
list of strings | [] |
CONTAINMENT_STATIC_AUTH__ANONYMOUS__POLICIES |
Named policies applied to token-less requests. Empty/absent = anonymous denied (deny-by-default). |
static_auth.tokens |
list of objects | [] |
CONTAINMENT_STATIC_AUTH__TOKENS |
Client tokens: each has a secret (env:VAR to inject from the environment, or a clear-text value), policies (named policies the token is bound to), and an optional bound_cidrs list restricting the token to peer IPs in those ranges (validated at startup, same as API tokens). |
Canary Keys
Section titled “Canary Keys”| Option | Type | Default | Env Var | Description |
|---|---|---|---|---|
canary_keys |
list of strings | [] |
CONTAINMENT_CANARY_KEYS |
Canary validator keys. Signing requests for these keys emit a warning log and increment containment_canary_signing_total. |
| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
tls.mode |
string | disabled |
--tls-mode |
CONTAINMENT_TLS__MODE |
TLS mode: disabled, file, or ratls. |
tls.listen_port |
integer | 9443 |
--tls-listen-port |
CONTAINMENT_TLS__LISTEN_PORT |
TLS listener port. |
tls.max_connections |
integer | 512 |
— | CONTAINMENT_TLS__MAX_CONNECTIONS |
Maximum concurrent TLS connections. |
tls.file.cert_path |
string | — | --tls-file-cert-path |
CONTAINMENT_TLS__FILE__CERT_PATH |
Path to TLS certificate file (PEM). |
tls.file.key_path |
string | — | --tls-file-key-path |
CONTAINMENT_TLS__FILE__KEY_PATH |
Path to TLS private key file (PEM). |
tls.file.reload_interval_seconds |
integer | 60 |
--tls-file-reload-interval-seconds |
CONTAINMENT_TLS__FILE__RELOAD_INTERVAL_SECONDS |
How often to check for cert/key file changes. |
tls.ratls.cert_validity_seconds |
integer | 86400 |
--tls-ratls-cert-validity-seconds |
CONTAINMENT_TLS__RATLS__CERT_VALIDITY_SECONDS |
Self-signed RA-TLS certificate validity period (24h default). |
tls.ratls.rotation_interval_seconds |
integer | 3600 |
--tls-ratls-rotation-interval-seconds |
CONTAINMENT_TLS__RATLS__ROTATION_INTERVAL_SECONDS |
RA-TLS certificate rotation interval (1h default). |
tls.listen_address |
string | 0.0.0.0 |
--tls-listen-address |
CONTAINMENT_TLS__LISTEN_ADDRESS |
TLS listen address. Ignored in Nitro Enclave mode (vsock uses CID, not IP). |
TEE — Nitro Enclave
Section titled “TEE — Nitro Enclave”| Option | Type | Default | Env Var | Description |
|---|---|---|---|---|
tee.platform |
enum | "off" |
CONTAINMENT_TEE__PLATFORM |
TEE platform. nitro enables AWS Nitro Enclave mode; off (default) runs as a plain binary with no vsock or NSM attestation. When nitro, a tee.nitro: section must also be present. |
tee.nitro.egress.endpoints |
list of objects | [] |
CONTAINMENT_TEE__NITRO__EGRESS__ENDPOINTS |
Egress endpoints the enclave reaches via the parent’s vsock-proxy fleet. Each entry requires: hostname (string), loopback (unique IPv4 in 127.0.0.0/8, not 127.0.0.0/127.0.0.1/127.255.255.255), vsock_port (unique u16, must not collide with ingress or reserved ports 7000-7002), and optional port (default 443). |
OpenTelemetry
Section titled “OpenTelemetry”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
opentelemetry.enabled |
boolean | false |
--opentelemetry-enabled |
CONTAINMENT_OPENTELEMETRY__ENABLED |
Enable OpenTelemetry OTLP export. |
opentelemetry.endpoint |
string | "http://localhost:4317" |
--opentelemetry-endpoint |
CONTAINMENT_OPENTELEMETRY__ENDPOINT |
OTLP gRPC endpoint URL. |
opentelemetry.service_name |
string | "containment-chamber" |
--opentelemetry-service-name |
CONTAINMENT_OPENTELEMETRY__SERVICE_NAME |
Service name attached to traces. |
Logging
Section titled “Logging”| Option | Type | Default | CLI Flag | Env Var | Description |
|---|---|---|---|---|---|
logging.level |
string | "info" |
--logging-level |
CONTAINMENT_LOGGING__LEVEL |
Log level filter (EnvFilter syntax supported). |
logging.format |
enum | "text" |
--logging-format |
CONTAINMENT_LOGGING__FORMAT |
Log output format: text or json. |
logging.color |
boolean | auto |
--logging-color |
CONTAINMENT_LOGGING__COLOR |
ANSI color output (auto-detected when unset). |
server: listen_address: "0.0.0.0" listen_port: 9000Metrics
Section titled “Metrics”Prometheus metrics endpoint configuration. Metrics are served on a separate port from the signing API.
metrics: listen_address: "0.0.0.0" listen_port: 3000 refresh_interval_seconds: 30Network
Section titled “Network”The Ethereum network name. Used to validate genesis_validators_root on signing requests. Signing remains fork-agnostic because fork data comes from each request.
network: mainnetKey Sources
Section titled “Key Sources”Key sources control where validator keys are loaded from. You can use filesystem, DynamoDB, or both simultaneously.
Filesystem
Section titled “Filesystem”Paths to directories containing key configuration YAML files. Multiple directories can be specified to load keys from different locations or formats (raw hex, PBKDF2, Scrypt).
key_sources: filesystem: paths: - ./keystores/raw - ./keystores/pbkdf2 - ./keystores/scrypt keystore_load_concurrency: 128 raw_load_concurrency: 128DynamoDB + KMS
Section titled “DynamoDB + KMS”Store validator keys in AWS DynamoDB encrypted under the chamber master key. The master key is reconstructed from KMS-wrapped Shamir shares. Supports key generation and import via dedicated API endpoints.
key_sources: dynamodb: table: containment-keys refresh_interval_seconds: 1
signer_state: backend: dynamodb table: containment-state
chamber: keys: generate: enabled: false max_items_per_request: 100 generate_keys: enabled: false deposit_data: enabled: false backup: recipients: [] # age public keys for offline mnemonic backup; non-empty enables backup; shared by generate + generate_keys import: enabled: false lifecycle: enabled: falseOn non-Nitro deployments, KMS key ARNs, the Shamir threshold, and root-token recipients are declared in the ceremony: config block and are part of the trusted deployment artifact — not submitted at runtime. On Nitro builds, these parameters are compiled into the EIF and PCR0-measured; the config file is irrelevant for ceremony parameters. See DynamoDB Key Source and Seal & Unseal Guide for the full setup.
Concurrency
Section titled “Concurrency”Controls backpressure for signing request processing.
signing.max_concurrent_jobs— Maximum number of signing operations processed simultaneously.signing.queue_buffer_size— Size of the request queue buffer. Requests beyond this limit receive HTTP 503.signing.priority.enabled— Enable a dedicated priority pool for high-priority operations like block signing.signing.priority.max_concurrent_jobs— Permits reserved for the priority pool.signing.priority.operations— List of operation types to route to the priority pool (e.g.,[BLOCK_V2]).
signing: max_concurrent_jobs: 2000 queue_buffer_size: 4000 # priority: # enabled: true # max_concurrent_jobs: 50 # operations: [BLOCK_V2]Anti-Slashing
Section titled “Anti-Slashing”EIP-3076 slashing protection backend configuration. PostgreSQL is recommended for production deployments as it supports multi-instance setups with full surround vote detection.
Backend Options
Section titled “Backend Options”| Backend | Value | Multi-Instance | Surround Detection | Use Case |
|---|---|---|---|---|
| PostgreSQL | postgres |
✅ | ✅ | Production (recommended) |
| SQLite | sqlite |
❌ | ✅ | Development / single instance |
| DynamoDB | dynamodb |
✅ | ✅ (implicit) | AWS deployments |
| Noop | noop |
— | — | Testing only |
PostgreSQL (recommended)
Section titled “PostgreSQL (recommended)”anti_slashing: backend: postgres url: "postgresql://user:password@localhost:5432/slashing?sslmode=require" pool_size: 64 force_ipv4: falseTLS is enabled by default. Append ?sslmode=disable to the URL to disable it. The AWS RDS CA bundle is included in the Docker image.
SQLite
Section titled “SQLite”anti_slashing: backend: sqlite path: ./slashing_protection.sqliteanti_slashing: backend: noopKey Manager API
Section titled “Key Manager API”Hot-load and remove EIP-2335 keystores at runtime via the /eth/v1/keystores endpoint. Disabled by default.
http: key_manager_api: enabled: trueAccess to Key Manager API routes (/eth/v1/keystores) is controlled by auth policies (managed via containment-chamber operator auth) using the list_keys, import_keystores, and delete_keys scopes. See Auth Policies below.
Auth Policies
Section titled “Auth Policies”Auth policies are managed via containment-chamber operator auth, not in the config file. Policies control access to all routes. When policies exist, requests must include an Authorization: Bearer <token> header. Tokens are bound to one or more policies.
The first management (“root”) token is created automatically at boot auto-init, age-encrypted to ceremony.root_token_recipients, and stored in the ROOT_TOKEN_BOOTSTRAP DynamoDB row. Decrypt it with an age identity, then use it to create policies and client tokens with operator auth.
For the complete API reference, policy evaluation rules, and detailed examples, see the Auth Policies guide.
Static Auth (config-file-based, stateless-only)
Section titled “Static Auth (config-file-based, stateless-only)”static_auth declares named policies, static client tokens, and an optional anonymous binding, using the same PolicyRule model as API-managed policies — one representation everywhere. It is stateless-only: configurations that also set signer_state are rejected at startup, so state-backed deployments use the runtime auth API instead.
policies— named, reusable rule-sets (effectallow/deny, optionalscopes/keys/operations).anonymous.policies— named policies applied to requests with no Bearer token. Empty/absent ⇒ anonymous denied (deny-by-default).tokens— client tokens, each with asecret(env:VAR_NAMEinjected from the environment, or a clear-text value — operator’s choice), the namedpoliciesit is bound to, and an optionalbound_cidrslist restricting the token to peer IPs in those ranges (validated at startup like API tokens —0.0.0.0/0,::/0, IPv4/<8, IPv6/<16are rejected). Secrets are HMAC-hashed at boot and never persisted.
Example: allow unauthenticated signing but block voluntary exits, and issue one env-injected client token:
static_auth: policies: - name: anonymous rules: - effect: allow scopes: [sign, public_keys] - effect: deny operations: [VOLUNTARY_EXIT] anonymous: policies: [anonymous] tokens: - secret: env:CC_VC_TOKEN policies: [anonymous] bound_cidrs: ["10.0.0.0/8"] # optional: restrict the token to these peer IPsAPI Scopes
Section titled “API Scopes”All available scopes for use in policy rules:
sign, public_keys, list_keys, import_keystores, delete_keys, chamber_keys_generate, chamber_keys_generate_bls, chamber_keys_deposit_data, chamber_keys_import, chamber_keys_list, chamber_keys_patch, chamber_keys_delete, chamber_seal, chamber_status
Token and policy administration is not a scope — those endpoints require a management token.
Valid Signing Operation Names
Section titled “Valid Signing Operation Names”AGGREGATION_SLOT, AGGREGATE_AND_PROOF, ATTESTATION, BLOCK_V2, RANDAO_REVEAL, SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF, SYNC_COMMITTEE_MESSAGE, SYNC_COMMITTEE_SELECTION_PROOF, VALIDATOR_REGISTRATION, VOLUNTARY_EXIT
Canary Keys
Section titled “Canary Keys”Canary keys are validator public keys that should never sign in normal operation. When a canary key signs, the signer logs a warning and increments the containment_canary_signing_total metric. Signing is not blocked.
canary_keys: - "0x1234..." - "0x5678..."See Production Hardening for usage guidance.
OpenTelemetry
Section titled “OpenTelemetry”OpenTelemetry OTLP tracing export. Disabled by default.
opentelemetry: enabled: true endpoint: "http://localhost:4317" service_name: "containment-chamber"The endpoint should point to an OTLP gRPC collector (e.g., Jaeger, Grafana Tempo, or the OpenTelemetry Collector).
Logging
Section titled “Logging”Structured logging configuration for console output.
logging: level: "info" format: "text" color: trueHTTPS listener configuration. TLS is disabled by default. When enabled, a second listener starts on port 9443 alongside the existing HTTP listener on port 9000.
Two modes are available:
file— reads a PEM certificate and private key from disk, polling for changes on a configurable interval. Works with any PKI: self-signed certs, cert-manager, Vault PKI.ratls— generates an ephemeral certificate bound to an NSM attestation document. For AWS Nitro Enclave deployments only.
File mode
Section titled “File mode”tls: mode: file listen_port: 9443 file: cert_path: /etc/certs/tls.crt key_path: /etc/certs/tls.key reload_interval_seconds: 60 # 0 = disable pollingRemote Attestation TLS mode
Section titled “Remote Attestation TLS mode”tls: mode: ratls listen_port: 9443 ratls: cert_validity_seconds: 86400 # 24 hours rotation_interval_seconds: 3600 # rotate every hourSee Remote Attestation TLS for the full setup guide, including cert rotation and client-side attestation verification.
Scaling Recommendations
Section titled “Scaling Recommendations”Recommended configuration values by validator count:
| Parameter | 1K validators | 10K validators | 100K+ validators |
|---|---|---|---|
signing.max_concurrent_jobs |
100–500 | 500–2000 | 2000+ (default: 2000) |
signing.queue_buffer_size |
1000–3000 | 3000–4000 | 4000+ (default: 4000) |
anti_slashing.postgres.pool_size |
16–32 | 32–64 | 64+ (default: 64) |
| CPU cores | 1–2 | 2–4 | 4–8+ |
Anti-Slashing Backend at Scale
Section titled “Anti-Slashing Backend at Scale”For 100K+ validators, DynamoDB anti-slashing is recommended over PostgreSQL:
- Multi-instance safe (no single point of failure)
- No connection pool limits
- Scales automatically with DynamoDB on-demand billing
DynamoDB Shard Count
Section titled “DynamoDB Shard Count”The DynamoDB key source uses 16 shards for parallel key loading. This is a compile-time constant and cannot be changed via configuration. 16 shards supports up to ~100K keys efficiently.
Key Refresh Interval
Section titled “Key Refresh Interval”For large deployments, set key_sources.dynamodb.refresh_interval_seconds to a non-zero value to periodically reload keys added via the API without restarting:
key_sources: dynamodb: refresh_interval_seconds: 1 # reload every secondToken-Bound CIDRs
Section titled “Token-Bound CIDRs”Any client auth token — created via the API in a state-backed deployment, or declared under static_auth.tokens in a stateless deployment — may carry a bound_cidrs list. When non-empty, the token is only usable from a TCP peer IP in the listed ranges. Enforcement runs inside AuthContext::require_scope / require_signing / filter_public_keys, so every route gated by one of the 15 ApiScopes — plus lookup-self and renew-self — is CIDR-enforced automatically.
For state-backed deployments, CIDR binding is supplied either via the containment-chamber operator auth token create --bound-cidrs 10.0.0.0/8,192.168.0.0/16 CLI flag or in the token-creation request body; for stateless deployments it is declared under static_auth.tokens[].bound_cidrs. See the Auth guide and API Reference for the request schema.
Rules:
- Management (root) tokens are never CIDR-bound — they are the break-glass path and binding them would block operator recovery.
- Authorization uses the raw TCP peer IP only.
X-Forwarded-Foris not consulted for authz decisions (it continues to driveclient_ip=in audit logs). A leaked token cannot escape its CIDR binding by forgingX-Forwarded-For. - IPv4-mapped IPv6 peers (
::ffff:a.b.c.d) are canonicalized before matching so a token bound to10.0.0.0/8matches both10.0.1.5and::ffff:10.0.1.5. validate_safe_cidrsrejects unsafe ranges (0.0.0.0/0,::/0, IPv4/<8, IPv6/<16): API-created tokens are rejected at creation time with400 Bad Request;static_authtokens are rejected at startup as a configuration error (fail-closed) before any listener binds.
Recommended refresh interval for security-sensitive deployments: keep the default signer_state.refresh_interval_seconds = 1 so a narrowed CIDR binding (implemented today as revoke + re-create) is effective within about one second across the cluster.
Next Steps
Section titled “Next Steps”- Auth Policies & Tokens — create policies and tokens after configuring the server
- CLI Reference — command reference for
server,operator,config, and shell completions - Observability — set up metrics and logging with the values you just configured
- Production Hardening — apply the ceremony CIDR filter and token security checklist

