Scaling & High Availability
Single Instance (Default)
By default, each chain runs in its own child process (workerProcesses: true in config). This means one chain crashing or losing its RPC connection does not affect the others. For most deployments a single container is sufficient.
{
"server": {
"workerProcesses": true
}
}
Horizontal Scaling
Running more than one replica without a database will cause duplicate events — every replica polls the chain independently, so your webhook endpoint receives multiple copies of the same event.
The database solves this in two ways:
- One replica owns each chain at a time — replicas compete for a lease stored in the database. Only the winner polls; the others wait on standby.
- Progress survives restarts — the last processed block is persisted, so a restarting replica resumes exactly where it left off instead of replaying from scratch.
Set DB_HOST before running more than one container.
To run multiple instances of the container in parallel — for redundancy or higher throughput — you must connect a database. The container uses a distributed lease mechanism (one active worker per chain at a time) to prevent duplicate event delivery when multiple instances are running.
# Required for horizontal scaling
DB_HOST=your-postgres-or-mysql-host
DB_LEASE_TTL_MS=30000 # how long a lease is held before another instance can take over
If the active instance crashes or loses its lease, another instance picks it up within DB_LEASE_TTL_MS milliseconds.
Docker Compose — 2 Replicas
services:
trustvc-events:
image: trustvc/trustvc-chain-events:latest
deploy:
replicas: 2
ports:
- "8080-8081:8080"
volumes:
- ./config.json:/app/config.json:ro
env_file:
- .env
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: trustvcevents
POSTGRES_USER: trustvc
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data
restart: unless-stopped
volumes:
pgdata:
.env
SIGNING_PRIVATE_KEY="your-base64-seed"
DB_HOST=postgres
DB_PORT=5432
DB_NAME=trustvcevents
DB_USER=trustvc
DB_PASSWORD=secret
DB_LEASE_TTL_MS=30000
AWS ECS — Fargate
For production deployments on AWS, run the container as a Fargate service. The task definition below mirrors the Docker run command.
Task definition (excerpt)
{
"family": "trustvc-chain-events",
"containerDefinitions": [
{
"name": "trustvc-chain-events",
"image": "trustvc/trustvc-chain-events:latest",
"portMappings": [
{ "containerPort": 8080, "protocol": "tcp" }
],
"mountPoints": [
{
"sourceVolume": "config",
"containerPath": "/app/config.json",
"readOnly": true
}
],
"environment": [
{ "name": "DB_HOST", "value": "your-rds-endpoint" },
{ "name": "DB_NAME", "value": "trustvcevents" },
{ "name": "DB_USER", "value": "trustvc" },
{ "name": "DB_LEASE_TTL_MS", "value": "30000" }
],
"secrets": [
{
"name": "SIGNING_PRIVATE_KEY",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:trustvc/signing-key"
},
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:region:account:secret:trustvc/db-password"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/trustvc-chain-events",
"awslogs-region": "ap-southeast-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
config.json on ECSMount config.json from an S3 object using an init container, or bake a non-secret config into a custom image layer. Keep secrets in AWS Secrets Manager and inject them as environment variables as shown above.
Health Check Integration
All orchestrators (ECS, Kubernetes, Docker Compose) can use the /health endpoint to determine instance readiness:
{
"healthCheck": {
"command": ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"],
"interval": 30,
"timeout": 5,
"retries": 3,
"startPeriod": 15
}
}
/health response | Meaning |
|---|---|
{"status":"ok"} | All chains connected |
{"status":"starting"} | Container is still connecting to one or more chains |
{"status":"degraded"} | At least one chain has permanently failed — restart the container |