rCTF Docs
Overview

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#

Clone and install dependencies
Terminal window
git clone https://github.com/otter-sec/rctf.git
cd rctf
bun i
Create a configuration directory

rCTF 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:

rctf.d/01-base.yaml
ctfName: My CTF
origin: https://ctf.example.com
tokenKey: <base64-encoded-32-byte-key>
database:
sql: postgres://user:password@localhost:5432/rctf
redis: redis://localhost:6379
migrate: before
startTime: 1735689600000
endTime: 1735776000000
divisions:
open: Open
Tip (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.

Run database migrations
Terminal window
bun run db:migrate

Alternatively, set database.migrate to before in your config to run migrations automatically on startup.

Start the development servers
Terminal window
bun dev

This 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:

Terminal window
bun run dev:api # API only on :3000
bun run dev:web # Frontend only on :5173
Tip (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:

Terminal window
bun run --filter '@rctf/api' build # Builds API server bundle
bun run --filter '@rctf/web' build # Builds static frontend

Start the API server:

Terminal window
NODE_ENV=production WORKER_EXTENSION=.js bun run apps/api/dist/index.js

WORKER_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:

Terminal window
bun run test # Run all tests from root
bun run test:server:coverage # Server tests with coverage
bun run typecheck # Typecheck all workspaces
Esc

Start typing to search the docs.