Skip to content

Configuration Reference

Runtime configuration reference for the containment-chamber server process.

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.

A fully commented example configuration file is available at config.example.yaml. Copy it and customize for your deployment:

Terminal window
curl -O https://raw.githubusercontent.com/unforeseen-consequences/containment-chamber/main/config.example.yaml
cp config.example.yaml config.yaml
# Edit config.yaml to match your setup
containment-chamber server -c config.yaml
Full 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 settings
server:
listen_address: "0.0.0.0" # CLI: --server-listen-address
listen_port: 9000 # CLI: --server-listen-port
# Metrics endpoint
metrics:
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: --network
network: mainnet
# Key sources: filesystem, DynamoDB, or both
key_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 queues
signing:
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: null

Server configuration values are resolved in the following precedence order (highest wins):

  1. Server CLI flags — e.g., containment-chamber server --server-listen-port 9001
  2. Environment variables — e.g., CONTAINMENT_SERVER__LISTEN_PORT=9001
  3. Config file — e.g., config.yaml
  4. Built-in defaults

To start with a config file:

Terminal window
containment-chamber server -c config.yaml

Or use CLI flags directly:

Terminal window
containment-chamber server \
--key-sources-filesystem-paths ./keystores/raw \
--key-sources-filesystem-paths ./keystores/pbkdf2 \
--anti-slashing-backend sqlite \
--anti-slashing-sqlite-path ./slashing.sqlite

All configuration options can be set via environment variables using the CONTAINMENT_ prefix. Nested keys use __ (double underscore) as the separator.

Terminal window
# server.listen_port → CONTAINMENT_SERVER__LISTEN_PORT
CONTAINMENT_SERVER__LISTEN_PORT=9001
# server.listen_address → CONTAINMENT_SERVER__LISTEN_ADDRESS
CONTAINMENT_SERVER__LISTEN_ADDRESS="127.0.0.1"
# anti_slashing.url → CONTAINMENT_ANTI_SLASHING__URL
CONTAINMENT_ANTI_SLASHING__URL="postgresql://user:pass@db/slashing"
# anti_slashing.backend → CONTAINMENT_ANTI_SLASHING__BACKEND
CONTAINMENT_ANTI_SLASHING__BACKEND=postgres
# network → CONTAINMENT_NETWORK
CONTAINMENT_NETWORK=mainnet

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: false
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.
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.
Option Type Default CLI Flag Env Var Description
network string "mainnet" --network CONTAINMENT_NETWORK Ethereum network name (mainnet, hoodi, sepolia).
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.
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.
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.
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).
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.
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.
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.
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).
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).
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).
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.
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: 9000

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: 30

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: mainnet

Key sources control where validator keys are loaded from. You can use filesystem, DynamoDB, or both simultaneously.

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: 128

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: false

On 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.


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]

EIP-3076 slashing protection backend configuration. PostgreSQL is recommended for production deployments as it supports multi-instance setups with full surround vote detection.

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
anti_slashing:
backend: postgres
url: "postgresql://user:password@localhost:5432/slashing?sslmode=require"
pool_size: 64
force_ipv4: false

TLS is enabled by default. Append ?sslmode=disable to the URL to disable it. The AWS RDS CA bundle is included in the Docker image.

anti_slashing:
backend: sqlite
path: ./slashing_protection.sqlite
anti_slashing:
backend: noop

Hot-load and remove EIP-2335 keystores at runtime via the /eth/v1/keystores endpoint. Disabled by default.

http:
key_manager_api:
enabled: true

Access 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 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 (effect allow/deny, optional scopes/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 a secret (env:VAR_NAME injected from the environment, or a clear-text value — operator’s choice), the named policies it is bound to, and an optional bound_cidrs list restricting the token to peer IPs in those ranges (validated at startup like API tokens — 0.0.0.0/0, ::/0, IPv4 /<8, IPv6 /<16 are 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 IPs

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.

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 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 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).


Structured logging configuration for console output.

logging:
level: "info"
format: "text"
color: true

HTTPS 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.
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 polling
tls:
mode: ratls
listen_port: 9443
ratls:
cert_validity_seconds: 86400 # 24 hours
rotation_interval_seconds: 3600 # rotate every hour

See Remote Attestation TLS for the full setup guide, including cert rotation and client-side attestation verification.


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+

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

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.

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 second

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-For is not consulted for authz decisions (it continues to drive client_ip= in audit logs). A leaked token cannot escape its CIDR binding by forging X-Forwarded-For.
  • IPv4-mapped IPv6 peers (::ffff:a.b.c.d) are canonicalized before matching so a token bound to 10.0.0.0/8 matches both 10.0.1.5 and ::ffff:10.0.1.5.
  • validate_safe_cidrs rejects unsafe ranges (0.0.0.0/0, ::/0, IPv4 /<8, IPv6 /<16): API-created tokens are rejected at creation time with 400 Bad Request; static_auth tokens 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.