Scaling

Resource expectations, instance types, and horizontal scaling notes for rCTF deployments.

Edit this page View Markdown

Most events can run rCTF on one server. Multiple application containers are only necessary for unusually large competitions or deployments that need redundancy.

Resource expectations

SekaiCTF 2026 ran rCTF as a single application container on a Hetzner CPX62 instance with 16 vCPUs and 32 GiB of RAM. At peak, the entire server, including PostgreSQL and Redis, used about 2.6 CPU cores.

sekaictf-2026-cpu-usage

The VPS setup walkthrough starts with 2 CPU cores and 4 GiB of memory. In the bundled compose.yml, PostgreSQL and Redis use more idle memory than the rCTF container itself.

Instance types

The instanceType config option (environment variable RCTF_INSTANCE_TYPE) selects what an API process runs.

Value API server Leaderboard worker thread
all (default) Yes Yes
frontend Yes No
leaderboard Health only Yes

Every instance type listens on PORT, which defaults to 3000. A leaderboard instance only serves /api/healthz and /api/readyz, allowing a deployment system to check its health even though it does not serve normal API routes.

rctf.d/02-scaling.yaml
instanceType: frontend # or 'leaderboard' or 'all'

Horizontal scaling

To scale beyond one container, run several all instances behind a load balancer and connect them to the same PostgreSQL and Redis services. A PostgreSQL advisory lock ensures that only one instance runs the leaderboard worker at a time. The bundled Compose file is for one server, so a multi-server deployment needs its own load balancer and service configuration.

Note (Splitting roles is optional)

Several all replicas are the simplest arrangement. Separate frontend and leaderboard replicas only when the worker should have dedicated CPU.

Warning (No transaction-pooling proxies)

Connect database.sql directly to PostgreSQL or through a proxy that preserves database sessions. Leader election and migrations use session-scoped advisory locks, which must remain on the same PostgreSQL connection. Transaction-pooling modes, including PgBouncer in transaction mode, can leave the lock on a different connection and stop leaderboard updates.

Note (Forced leaderboard updates)

Changes that affect scores notify the active leaderboard worker through Redis. If leadership changes, the new worker recalculates every challenge before continuing. All rCTF instances must use the same Redis service for these notifications to work.

Health checks

The API provides two public endpoints for load balancers and deployment health checks. The bundled nginx exposes them under /api.

Endpoint Purpose Behavior
/api/healthz Liveness Returns 200 while the process is up.
/api/readyz Readiness Returns 200 when PostgreSQL and Redis are both reachable, and 503 otherwise. Use it to gate load-balancer traffic.

Graceful shutdown

On SIGTERM or SIGINT, the API stops accepting connections, drains active requests, and releases the leaderboard lock for a standby. The shutdownTimeout defaults to 30 seconds, so the orchestrator’s termination grace period must be at least that long. A second signal exits immediately.

After a host crash or network partition, Postgres holds the leaderboard lock until it detects the dead connection. Multi-node deployments should shorten the Postgres tcp_keepalives_idle, tcp_keepalives_interval, and tcp_keepalives_count settings. Do not use idle_session_timeout, which can also kill healthy sessions holding advisory locks.

Type to search.