Quick Start
This guide takes you from a clean Docker host to a running platform with your first Attack & Defense (A&D) or King of the Hill (KotH) game. You stand the platform up from the TCP1P/gzctf-platform-template repo: it ships the published image, Traefik + Let's Encrypt TLS, and a one-shot wizard. Three make commands and you have a live, HTTPS platform.
The short version:
Then log in at https://PUBLIC_ENTRY as user Admin with the password the wizard printed.
For exhaustive deployment options see /guide/deployment/docker; for every configuration key see /config/appsettings.
The template deploys the published image dimasmaualana/gzctf:develop — it does not build GZCTF from source. All per-deployment configuration lives in compose/appsettings.json (rendered from compose/appsettings.example.json), and secrets live in compose/.env (both auto-generated by the wizard, both chmod 600).
Prerequisites
- Docker Engine with the Compose v2 plugin (
docker compose ..., not the legacydocker-composebinary). - The Docker daemon socket at
/var/run/docker.sock— thegzctfcontainer bind-mounts it so it can spawn and manage challenge containers directly on the host. - A DNS name pointing at the host: set
PUBLIC_ENTRYto a hostname (e.g.ctf.example.com) whose A record resolves to this machine. Let's Encrypt issues the TLS cert over an HTTP-01 challenge, so the name must be publicly reachable on port80at boot. - Inbound ports:
80/tcp+443/tcp(Traefik / ACME / web UI). For A&D you also need51820/udp(WireGuard) and22022/tcp(SSH jump, override withAD_SSH_PUBLIC_PORT). - A host kernel with the
tunmodule available (/dev/net/tun) if you run the WireGuard sidecar for A&D VPN access.
gzctf mounts /var/run/docker.sock, which is effectively root on the host — that is how it launches and tears down challenge containers. Run the platform only on a host you control, and never expose the socket to anything other than the gzctf service.
If you only want a vanilla jeopardy CTF you can delete the wireguard and ssh-jump services from compose/compose.yml (and the wg-config volume + wg-bootstrap network) — they exist solely to give A&D/KotH teams VPN and SSH access to their containers. Everything else (gzctf, db, cache, traefik) is the standard deployment.
1. Clone + run the wizard
make wizard (scripts/wizard.sh) is an interactive first-time setup. It prompts for:
Everything else is auto-generated so you never hand-pick a secret:
WORKSPACE— agzctf-<8 hex>compose project name + Traefik route suffix.XOR_KEY—openssl rand -hex 32; encrypts registry passwords + repo-binding tokens at rest.ADMIN_PASSWORD— the seed password for the initialAdminuser. TheAa1prefix satisfies ASP.NET Identity's password policy (uppercase + lowercase + digit).POSTGRES_PASSWORD— the Postgres credential, kept in step between thedbcontainer andgzctf's connection string.
The wizard writes compose/.env and compose/appsettings.json (both chmod 600, both gitignored) and refuses to overwrite an existing appsettings.json — delete it first if you need to re-run.
At the very end the wizard prints the generated admin seed password. It is not stored anywhere you'll look again (it lives only in .env), so copy it now. You log in with it, then change it in the UI.
XOR_KEY or POSTGRES_PASSWORD after first boot
XOR_KEY decrypts every registry password and repo-binding token already in the database; rotating it breaks them all. POSTGRES_PASSWORD is consumed by Postgres only on its first init — changing it later requires an ALTER USER inside the running db container. Leave both as the wizard generated them.
2. Create the networks + bring it up
make platform-up first runs init-config (which renders compose/appsettings.json from appsettings.example.json if it is not already present) and then docker compose -f compose.yml -f compose.traefik.yml up -d. That brings up the full stack:
Watch progress and verify health:
make help lists every target — including platform-down, platform-restart, flush-cache (redis FLUSHALL, rebuilds the scoreboard cache on next request), and the image-update flows (pull / update, or the targeted pull-gzctf / update-gzctf which only recreates the gzctf container).
platform-clean drops your data
make platform-clean runs docker compose down -v — it deletes the postgres-data, gzctf-files, and wg-config volumes. Use make platform-down to stop the stack while keeping all data.
3. Log in
Open the web UI:
Log in as user Admin with the password the wizard printed in step 1, then change it from the profile menu. From here, head to the admin area to set up your game.
Skip Traefik and Let's Encrypt with:
This brings up gzctf + db + cache only (via the compose.standalone.yml overlay) and publishes the platform directly on host port 8080. For standalone use, edit compose/appsettings.json and set ContainerProvider.PublicEntry to http://<host>:8080 so emails and scoreboard links resolve to the right place.
Load example challenges
The fastest way to see the A&D/KotH engine end to end is to import the public, ready-to-run event repo TCP1P/TCP1PADTesting. The GZCTF server clones the repo itself, globs its .gzevent manifest, and creates one game with four challenges — no challenge authoring required.
- In the admin area open Repo Bindings and click Add:
- Repo URL:
https://github.com/TCP1P/TCP1PADTesting - Ref: leave empty (default branch)
- Interval (seconds):
60 - Token: none — the repo is public
- Repo URL:
- Click Scan now to import immediately rather than waiting for the poll interval.
The scan creates one game, "TCP1P A&D + KotH Testing", with four challenges under events/tcp1p-testing/:
None of the four pins a containerImage or checkerImage, so each ships an auto-built src/ service plus an enochecker3 checker that builds on import. Watch the 8 images (a service + a checker per challenge) come up under admin → Builds.
The game imports hidden. To run it, open admin → game → Info, set the start/end times, and unhide it.
For the complete binding workflow — poller behaviour, shallow-clone short-circuiting, binding health, and a step-by-step of this exact repo — see /guide/authoring/repo-bindings.
The A&D/KotH challenge types
When you author challenges by hand instead, the challenge editor lets you pick the challenge type. Alongside the classic jeopardy types (Static/Dynamic Attachment, Static/Dynamic Container), this fork adds two A&D-engine types:
Both A&D-engine types run as managed containers and skip the per-flag configuration jeopardy challenges use — the flags are issued and rotated by the A&D engine each round, not entered by hand. Selecting AttackDefense or KingOfTheHill surfaces the container image and the relevant A&D fields and hides the manual flag inputs.
For the full schema of an A&D/KotH challenge — image, checker, exposed ports, network mode — see /guide/authoring/challenge-yaml.
A&D round settings live on the game's Info page
The settings that govern the rhythm of an A&D/KotH event are per-game, not per-challenge, and they live in admin → game → Info (the Info tab of the game editor). The key fields and their defaults, straight from the editor:
There is also a get-flag window fraction and related fields below these in the same section. Set the tick length and flag lifetime here before the game starts — they define how the whole event is scored.
Tick cadence and flag lifetime drive how points accrue. To understand how round flags, SLA, and stolen-flag points turn into scoreboard standing, read /guide/features/scoring.
Where to go next
- /guide/deployment/docker — full deployment detail: the standalone (no-TLS) overlay, the Kubernetes container backend, host kernel tuning for many containers, and capacity ceilings.
- /config/appsettings — every configuration key in
appsettings.json, including allContainerProvider,Ad.Vpn/Ad.Ssh,HoneypotConfig, andFlagEgressConfigsettings. - /guide/authoring/repo-bindings — importing whole events from a Git repo.
- /guide/authoring/challenge-yaml — authoring A&D/KotH and jeopardy challenges.
- /guide/features/scoring — how rounds, SLA, and flag captures are scored.