Heavens CTF (hCTF) is a self-hosted CTF (Capture The Flag) platform. Single binary, no dependencies, runs anywhere Go does.
- Single binary — embeds all assets, templates, and migrations; copy one file and run
- Full CLI — cobra subcommands for admins and participants; charmbracelet TUI with tables, markdown, interactive browser
- Auto-migrations — schema upgrades apply automatically on startup, no data loss
- Challenge management — categories, difficulties, flag masking, point hints, file attachments
- Team play — create teams, invite-link based joining, team scoreboard
- Competition lifecycle — time-bounded competitions with draft→registration→running→ended transitions, per-competition scoreboards, scoreboard blackout
- Live submission feed — global
/submissionspage and per-competition feed; admin sees all attempts with flag text - Dynamic scoring — points decay based on solve count
- Score evolution chart — visual timeline of top competitors' scores using Chart.js
- SQL Playground — per-challenge DuckDB WASM sandbox for SQL-style CTF challenges
- Dark/light theme — persistent toggle, no flash of unstyled content
- Admin panel — full CRUD for challenges, questions, hints, categories, users, competitions
- Import/Export — JSON backup and restore for challenges
- Rate limiting — per-user flag submission throttling
- CTFtime export — scoreboard in CTFtime.org JSON format
- OpenAPI docs — browsable Swagger UI at
/api/openapi
docker compose up -dOpen http://localhost:8090 — default credentials: admin@hctf.local / changeme
docker-compose.yml example
services:
hctf:
image: ghcr.io/ajesus37/hCTF:latest
ports:
- "8090:8090"
volumes:
- ./data:/data
environment:
JWT_SECRET: ${JWT_SECRET:-change-me-in-production}
command: >
--db /data/hctf.db
--admin-email admin@hctf.local
--admin-password changeme
restart: unless-stoppedDownload the latest binary from Releases:
curl -L https://github.com/ajesus37/hCTF/releases/latest/download/hctf-linux-amd64 -o hctf
chmod +x hctf
./hctf serve --admin-email admin@example.com --admin-password yourpasswordRequires Go 1.24+ and Task:
git clone https://github.com/ajesus37/hCTF.git
cd hctf
./setup.sh # checks requirements and downloads dependencies
task build
./hctf serve --admin-email admin@example.com --admin-password yourpasswordAll server options are set via flags on hctf serve. See CONFIGURATION.md for a fully annotated reference.
| Option | Flag | Default |
|---|---|---|
| Port | --port |
8090 |
| Database | --db |
./hctf.db |
| Admin email | --admin-email |
— |
| Admin password | --admin-password |
— |
| Message of the Day | --motd |
— |
The binary doubles as a full-featured CLI client for a running server:
# Authenticate
hctf login --server http://localhost:8090 --email admin@example.com --password yourpassword
# Browse challenges interactively (TTY)
hctf challenge browse
# Submit a flag
hctf flag submit <question-id> FLAG{...}
# Live submission feed (auto-refreshes every 5s)
hctf submissions --watch
# View your profile
hctf user profile
# Admin: create a challenge (interactive on TTY, or via flags)
hctf challenge create --title "My Challenge" --category Web --difficulty Easy --points 200
hctf challenge export --output backup.json
hctf challenge import backup.json
# Admin: manage competitions
hctf competition create "CTF 2026"
hctf competition start <id>
hctf competition scoreboard <id>
# Admin: manage users
hctf user list
hctf user promote <user-id>
# Admin: export/import full platform config (challenges, competitions, settings)
hctf config export -o backup.yaml
hctf config import backup.yaml
# JSON output for scripting
hctf --json challenge list | jq '.[] | .title'All create/update commands prompt interactively on TTY. Pass --quiet to suppress output (returns ID only), --json for machine-readable output.
Run hctf --help for the full command tree.
docker run -d \
-p 8090:8090 \
-v hctf-data:/data \
-e JWT_SECRET="$(openssl rand -base64 32)" \
ghcr.io/ajesus37/hCTF \
serve --db /data/hctf.db --admin-email admin@hctf.local --admin-password changemeOr use Docker Compose (see docker-compose.yml in the repo):
docker compose up -dctf.example.com {
reverse_proxy localhost:8090
}
Nginx config
server {
server_name ctf.example.com;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}# Docker volume
docker compose exec hctf cat /data/hctf.db > hctf.db.backup-$(date +%Y%m%d)
# Or if using a bind mount
cp ./data/hctf.db ./data/hctf.db.backup-$(date +%Y%m%d)Pull the latest image and restart — migrations run automatically:
docker compose pull
docker compose up -dNo manual migration steps needed.
If you prefer running the binary directly without Docker:
./hctf serve --db /var/lib/hctf/hctf.db \
--admin-email admin@example.com \
--admin-password yourpassword \
--jwt-secret "$(openssl rand -base64 32)"Use your preferred process manager (e.g. supervisord, runit) to keep it running.
- Passwords hashed with bcrypt (cost 12)
- JWT tokens stored in HttpOnly cookies
- All SQL queries use parameterized statements
- No telemetry or analytics by default
- Admin routes protected by role middleware
Production (required):
./hctf serve --jwt-secret "$(openssl rand -base64 32)"Or via environment variable:
export JWT_SECRET="$(openssl rand -base64 32)"
./hctf serveDevelopment (insecure):
./hctf serve --dev # Allows default JWT secret with warningThe server will refuse to start in production mode without a proper JWT secret (minimum 32 characters). See CONFIGURATION.md for details.
| Doc | Contents |
|---|---|
| CONFIGURATION.md | All config options in detail |
| ARCHITECTURE.md | How the codebase is structured |
| OPERATIONS.md | Deployment, monitoring, backup, troubleshooting |
| SQL_PLAYGROUND.md | DuckDB WASM SQL challenge mode |
| TESTING.md | Running and writing tests |
Issues and PRs welcome. To run locally:
task deps
task run # starts on :8090 with a default admin
task test # run testsMIT — see LICENSE.
The architecture, database schema, and core backend were designed and implemented before any AI assistance. We use AI tools for specific, scoped tasks — drafting boilerplate, suggesting refactors, writing docs — and every change is reviewed and usually rewritten by a human maintainer (aside from the frontend. While the architecture was human-chosen, this is not a core skill for me, so I let the AI implement, but reviewed and validated everything). This is not autonomous "generate and ship" code.