Skip to content
Diagram
  • Helm 3+ installed
  • Access to a Kubernetes cluster (1.24+)
  • A PostgreSQL instance for slashing protection (recommended for multi-instance deployments)
  • Validator keystores available as files or Kubernetes Secrets

Add your configuration overrides to a my-values.yaml file, then install:

Terminal window
helm install containment-chamber oci://ghcr.io/unforeseen-consequences/charts/containment-chamber \
--namespace validators \
--create-namespace \
-f my-values.yaml
image:
repository: ghcr.io/unforeseen-consequences/containment-chamber
pullPolicy: IfNotPresent
# Defaults to the chart appVersion if empty
tag: ""

The chart generates a config file from these values:

config:
server:
listen_address: "0.0.0.0"
listen_port: 9000
metrics:
listen_address: "0.0.0.0"
listen_port: 9001
network: mainnet
key_sources:
filesystem:
paths: []

Use env to inject secrets — the antislashing database URL, signing auth tokens, and any other sensitive values:

env:
- name: CONTAINMENT_ANTISLASHING__URL
valueFrom:
secretKeyRef:
name: cc-secrets
key: database-url
- name: CONTAINMENT_ANTISLASHING__BACKEND
value: "postgres"

Mount your validator keystores into the container using extraVolumes and extraVolumeMounts:

config:
key_sources:
filesystem:
paths:
- /keystores
extraVolumeMounts:
- name: keystores
mountPath: /keystores
readOnly: true
extraVolumes:
- name: keystores
secret:
secretName: validator-keystores
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 256Mi

If you run the Prometheus Operator, enable the ServiceMonitor to scrape metrics automatically:

serviceMonitor:
enabled: true
additionalLabels:
release: prometheus
namespace: ""
namespaceSelector: {}
scrapeInterval: 60s
targetLabels: []
metricRelabelings: []

Configure a PDB to control voluntary disruptions during node drains and cluster upgrades:

pdb:
minAvailable: 1
# Or use maxUnavailable instead:
# maxUnavailable: 50%

The chart ships with secure defaults. The container runs as a non-root user with a read-only filesystem:

Pod-level (podSecurityContext):

podSecurityContext:
runAsNonRoot: true
runAsUser: 65534

Container-level (securityContext):

securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL

These defaults satisfy most Pod Security Standards (restricted profile). You should not need to change them.

All probes target the /upcheck endpoint on the HTTP port:

startupProbe:
httpGet:
path: /upcheck
port: http
failureThreshold: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /upcheck
port: http
readinessProbe:
httpGet:
path: /upcheck
port: http

The startup probe allows up to 50 seconds (10 x 5s) for initial key loading — important when loading thousands of encrypted keystores. Once the startup probe succeeds, the liveness and readiness probes take over with default Kubernetes intervals.

The chart includes network policy support to restrict traffic:

netpolicies:
ingress:
# Only allow traffic from pods in these namespaces
allowedNamespaces:
- consensus-layer
egress:
# Allow DNS resolution
allowDns:
enabled: true
cidr: "0.0.0.0/0"
# Restrict outbound traffic
restrictEgress:
enabled: true
allowedEgressNamespaces:
- database
allowedEgressCIDRs:
- 10.0.0.0/8
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
terminationGracePeriodSeconds: 30
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app.kubernetes.io/name: containment-chamber

Deploy additional resources (ConfigMaps, Secrets, etc.) alongside the chart:

extraDeploy:
- apiVersion: v1
kind: ConfigMap
metadata:
name: extra-config
data:
key: value
Terminal window
helm upgrade containment-chamber ./k8s/charts/containment-chamber \
--namespace validators \
-f my-values.yaml

Check the rollout status:

Terminal window
kubectl -n validators rollout status deployment/containment-chamber

When running on EKS, use IAM Roles for Service Accounts (IRSA) to grant the pod access to DynamoDB and KMS without static credentials.

1. Create an IAM role with the required permissions (see AWS IAM Permissions).

2. Annotate the ServiceAccount in your values file:

serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/containment-chamber-role

3. Trust policy for the IAM role:

{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.REGION.amazonaws.com/id/CLUSTER_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.REGION.amazonaws.com/id/CLUSTER_ID:sub": "system:serviceaccount:NAMESPACE:containment-chamber"
}
}
}]
}

To use DynamoDB as the key source, pass the configuration through the config: block and set AWS credentials via IRSA (see above):

serviceAccount:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/containment-chamber-role
config:
key_sources:
dynamodb:
table: containment-keys
status_filter:
- active
key_refresh_interval_minutes: 60
keygen:
enabled: true
max_items_per_request: 100
writable:
enabled: true
request_timeout_seconds: 600

See DynamoDB + KMS Key Management for the full setup guide.

To use DynamoDB as the anti-slashing backend:

config:
antislashing:
backend: dynamodb
table: containment-slashing-protection

See AWS IAM Permissions for the required DynamoDB permissions.

Resource recommendations by validator count:

ValidatorsCPU RequestCPU LimitMemory RequestMemory Limit
< 1,000100m500m128Mi256Mi
1,000–10,000500m2000m256Mi512Mi
10,000–100,0001000m4000m512Mi1Gi
100,000+2000m8000m1Gi2Gi

Example values for 100K+ validators:

resources:
requests:
cpu: "2000m"
memory: "1Gi"
limits:
cpu: "8000m"
memory: "2Gi"

The default concurrency limits are tuned for large deployments:

  • max_concurrent_signing_jobs: 500 (configurable via --max-concurrent-signing-jobs)
  • signing_queue_buffer_size: 10,000 (configurable via --signing-queue-buffer-size)

For PostgreSQL anti-slashing at scale, increase the pool size:

config:
antislashing:
backend: postgres
pool_size: 32