Skip to main content

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

A database is required for scaling and HA

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 ECS

Mount 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 responseMeaning
{"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