Manual installation
Set up rCTF from source for development or custom deployments.
This guide covers setting up rCTF from source without Docker, suitable for development or environments where you need full control over the stack.
Prerequisites#
- Bun 1.2 or later
- PostgreSQL 15+
- Redis 7+
Project structure#
rCTF is a (mostly) Bun monorepo with the following layout:
apps/
- api/Hono REST API server
- web/SvelteKit frontend (static build)
- admin-bot/Puppeteer-based admin bot service
- k8s-controller/Kubernetes challenge instancer (Go)
- docker-instancer/Docker challenge instancer (Python)
- docs/Documentation site
- export/Instance archiver tool
- seed/Database seeding utility
packages/
- config/YAML/JSON config loader with Zod validation
- db/Drizzle ORM schema and migrations
- scoring/Pluggable scoring algorithms
- types/Shared route definitions and validators
- util/Small shared utilities
Setup#
git clone https://github.com/otter-sec/rctf.gitcd rctfbun irCTF loads configuration from a rctf.d/ directory. Files inside it (YAML or JSON) are loaded alphabetically and deep-merged. Create one with mkdir rctf.d at the project root.
rctf.d/
- 01-base.yaml create this next
Create a minimal configuration file:
ctfName: My CTForigin: https://ctf.example.comtokenKey: <base64-encoded-32-byte-key>
database: sql: postgres://user:password@localhost:5432/rctf redis: redis://localhost:6379 migrate: before
startTime: 1735689600000endTime: 1735776000000
divisions: open: OpenTip (Generating a token key)
The tokenKey must be a base64-encoded 32-byte random key used for AES-GCM token encryption. Generate one with openssl rand -base64 32.
You can also set configuration values through environment variables. See Configuration for the full reference.
bun run db:migrateAlternatively, set database.migrate to before in your config to run migrations automatically on startup.
bun devThis starts both the API server on http://127.0.0.1:3000 and the SvelteKit dev server on http://127.0.0.1:5173. The dev server proxies API requests to the backend.
To run them separately:
bun run dev:api # API only on :3000bun run dev:web # Frontend only on :5173Tip (One-shot dev setup)
For a fresh local environment, bun run dev:mock chains bun run db:migrate, bun run dev:seed, and bun run dev together. The seed resets the database and populates it with an admin account, 250 teams, 18 challenges across rev/pwn/crypto/web/misc, plus realistic solves and submissions. Login URLs for the admin and a sample team are printed to the console.
Production build#
Build both the API and frontend:
bun run --filter '@rctf/api' build # Builds API server bundlebun run --filter '@rctf/web' build # Builds static frontendStart the API server:
NODE_ENV=production WORKER_EXTENSION=.js bun run apps/api/dist/index.jsWORKER_EXTENSION=.js makes the API load the compiled leaderboard worker from apps/api/dist/. The API server listens on the port specified by the PORT environment variable (default 3000). In production, the static frontend is typically served by Nginx or a similar reverse proxy.
Scaling#
The instanceType config option lets you split an rCTF process into frontend-only or leaderboard-only roles for horizontal scaling. See Scaling for the full table, the one-leaderboard-replica constraint, and the forced-update limitation in split mode.
Running tests#
Tests use PGlite (in-process PostgreSQL) and ioredis-mock, so no external services are needed:
bun run test # Run all tests from rootbun run test:server:coverage # Server tests with coveragebun run typecheck # Typecheck all workspaces