Docker instancer
Deploy the rCTF Docker instancer with the bundled Compose stack and configure Docker-backed instanced challenges.
The Docker instancer is a Python FastAPI service that manages Docker containers, networks, volumes, Redis instance locks, Redis expirations, and Traefik labels. It’s the lightweight option for per-team challenge instances and is what the bundled Compose stack ships out of the box.
For the participant lifecycle, the common instancerConfig fields, and endpoint-kind semantics, see Instancer.
Deployment#
The bundled deployment files live under deploy/docker-instancer/:
deploy/
docker-instancer/
- compose.yml Traefik, Redis, and tiny-instancer service
- .env.example Required environment variables
- Dockerfile Python 3.14 runtime image
- conf/Traefik static and dynamic config
- certs/TLS certificate mount point
The Compose stack exposes Traefik on 80, 443, and 1337. The instancer API itself binds to 127.0.0.1:12237 on the host and to tiny-instancer:1337 inside the rctf_network Docker network.
Warning (Hosting the instancer on a separate machine)
The bundled compose.yml binds the instancer API to 127.0.0.1:12237 because the default deployment expects rCTF and the instancer to share the rctf_network Docker network on the same host. The loopback bind is intentional.
If you split the instancer onto its own host (which we recommend, since it isolates challenge workloads from the platform), change the port mapping to bind globally:
ports: - '12237:1337'The API is authenticated by a shared AUTH_TOKEN, but the endpoint shouldn’t be reachable from anything other than rCTF. Put a host firewall in front of it that only allows the rCTF host’s source IP.
The environment file requires shared auth, Redis, and public instance host settings:
DEV_ENV=falseBIND_PORT=1337WEB_WORKERS=1USE_PROXY_HEADERS=true
AUTH_TOKEN=<shared-secret>INSTANCES_HOST=instancer.example.com
REDIS_HOST=cacheREDIS_PORT_NUMBER=6379REDIS_PASSWORD=<redis-password>
TRAEFIK_PERMANENT_REDIRECT_MIDDLEWARE_NAME=permanent-https-redirect@fileThe Docker instancer stack starts from the repository root:
docker compose -f deploy/docker-instancer/compose.yml up -dThe same token belongs in rCTF:
instancerProvider: name: instancer/docker-instancer options: apiUrl: http://tiny-instancer:1337 authToken: <shared-secret>When rCTF is outside the rctf_network Docker network, the host-mapped API URL is:
instancerProvider: name: instancer/docker-instancer options: apiUrl: http://127.0.0.1:12237 authToken: <shared-secret>The Traefik TLS config expects these files:
deploy/
docker-instancer/
certs/
- fullchain.pem Certificate chain
- privkey.pem Private key
Docker daemon address pool#
Every instanced challenge creates its own user-defined Docker network. Docker carves those networks out of the daemon’s default address pool, which on a fresh install is only 172.17.0.0/16 plus a small chunk of 192.168.0.0/16. That’s enough for ~30 networks before docker network create starts failing with could not find an available, non-overlapping IPv4 address pool. For a production CTF this runs out fast.
Expand the pool by adding the following to /etc/docker/daemon.json on the instancer host:
{ "default-address-pools": [ { "base": "100.64.0.0/10", "size": 24 } ]}This carves 100.64.0.0/10 (the CGNAT range, safe to use internally) into /24 subnets, giving you 16384 Docker networks. That’s enough for ~8192 concurrent instances in the worst case (each challenge typically creates two networks). Add more default-address-pools entries if you need still more room.
Restart Docker after editing the file (systemctl restart docker) so it picks up the new pool. Networks already created on the old pool keep working but stick with their original ranges.
Docker challenge config#
The Docker provider accepts a Docker Compose-like object under config:
instancerConfig: challengeIntegrationId: web-demo timeoutMilliseconds: 600000 extendable: true config: services: app: image: ghcr.io/example/web-demo:latest environment: FLAG: flag{example} networks: - internal expose: - '8080' read_only: true mem_limit: 128m cpus: 0.5 pids_limit: 128 networks: internal: internal: true expose: - kind: https hostPrefix: web-demo containerName: app containerPort: 8080 title: ChallengeThe top-level Docker config supports services, networks, and volumes. At least one service is required after defaults are applied.
Each service supports these field groups:
Explicit services default to a read-only root filesystem, no-new-privileges, dropped Linux capabilities, 6m memory, 1.0 CPU, 1024 PIDs, and nofile soft and hard limits of 1024.
Services may only reference networks declared under config.networks. Named volume mounts may only reference volumes declared under config.volumes. Absolute and relative bind-style mount sources aren’t checked against the volume map.