You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document is the authoritative reference for every environment variable consumed by AstroCTFb. All values originate from .env (operator-managed) or HashiCorp Vault (auto-populated by setup.sh on first start). Build-time variables for the frontend (VITE_*) are baked into the SPA bundle by docker build and require an image rebuild to change.
The backend pulls all sensitive secrets from Vault at startup (8 KV-v2 paths under secret/ctf-platform/). Infrastructure containers (Postgres, Redis, SeaweedFS, Grafana) consume credentials directly from .env because they need them before Vault is reachable. HAProxy and certbot read a small subset for routing and TLS.
REQUIRED - fill before first start
Without these, setup.sh start will fail or the platform will start in a broken state. There are no sensible defaults - they describe your deployment.
true on first deploy -> LE staging cert (rate-limit safe); flip to false after DNS verified
API_BASE_URL
URL
backend (config.go:199)
Where the SPA expects the API (https://api.ctfleague.ru)
FRONTEND_URL
URL
backend (config.go:195)
Public SPA URL - used in email links
CORS_ORIGINS
comma-list
backend (config.go:158)
Allowed Origin headers for CORS preflight
GF_SERVER_ROOT_URL
URL
docker-compose -> Grafana
Public Grafana URL
STORAGE_S3_PUBLIC_ENDPOINT
URL
backend (config.go:199)
Rewrites internal S3 presigned URLs for public access
OAUTH_GITHUB_REDIRECT_URL
URL
backend (config.go:211)
Must match the value registered in GitHub OAuth App
OAUTH_GOOGLE_REDIRECT_URL
URL
backend (config.go:213)
Must match the value registered in Google Cloud Console
POSTGRES_PASSWORD
string
Postgres container, init-vault.sh
Postgres root password - also seeded into Vault
REDIS_PASSWORD
string
Redis container, init-vault.sh
Redis requirepass - also seeded into Vault
GRAFANA_ADMIN_PASSWORD
string
Grafana container
Required (docker-compose.yml:558 enforces with :?)
HAPROXY_STATS_PASSWORD
string
HAProxy entrypoint
Required (HAPROXY_STATS_PASSWORD:?); auth for haproxy:8405/stats
SEAWEED_S3_ACCESS_KEY
string
init-vault.sh:53, s3.json
SeaweedFS S3 access key
SEAWEED_S3_SECRET_KEY
string
init-vault.sh:54, s3.json
SeaweedFS S3 secret key
ADMIN_EMAIL
email
init-vault.sh:137 -> secret/ctf-platform/admin
Default admin user email (used for password-reset flow)
Note: the five *_DOMAIN records must all resolve to the server's IP for certbot to issue a single multi-SAN certificate covering all of them. Failure of even one record causes the whole certificate request to fail (3 retry attempts × 60s, then certbot exits cleanly).
OPTIONAL - auto-generated by Vault
init-vault.sh (deployment/docker/init-vault.sh) uses set-if-absent semantics: if the corresponding env var is non-empty on first start, it is seeded into Vault verbatim. If empty, a secure random value is generated and the path is logged with the marker [auto-generated]. Subsequent restarts do not rotate these values unless you explicitly run ./setup.sh secrets rotate*.
Variable
Auto-gen if empty
Vault path / field
Notes
FLAG_ENCRYPTION_KEY
64 hex chars (32 bytes, AES-256)
secret/ctf-platform/app -> flag_encryption_key
Validated at startup: must be exactly 64 hex chars
JWT_ACCESS_SECRET
64 alphanumeric chars
secret/ctf-platform/jwt -> access_secret
Backend signs access tokens with this
JWT_REFRESH_SECRET
64 alphanumeric chars
secret/ctf-platform/jwt -> refresh_secret
Backend signs refresh tokens
OAUTH_STATE_SECRET
64 alphanumeric chars
secret/ctf-platform/oauth -> state_secret
HMAC key for OAuth state nonce
ADMIN_USERNAME
admin
secret/ctf-platform/admin -> username
Default seed admin username
ADMIN_PASSWORD
16 alphanumeric chars (printed once)
secret/ctf-platform/admin -> password
If empty, init-vault.sh prints to stdout - write it down immediately
VAULT_TOKEN
filled by setup.sh after vault operator init
.env line VAULT_TOKEN=
Operator should not edit; backed up in .vault-keys
Golden rule: if you supply your own value, it goes into Vault verbatim. If you leave it empty, you cannot retrieve the random value later without querying Vault directly (e.g. vault kv get secret/ctf-platform/admin).
INTEGRATIONS - leave empty to disable
These activate optional features. Empty values disable the integration cleanly.
Email (Resend)
Variable
Default
Effect when empty
Read by
RESEND_API_KEY
""
Vault stores placeholder; outgoing email disabled at runtime
init-vault.sh:116 -> backend config.go:183
RESEND_ENABLED
true
If false, backend skips email sending entirely
config.go:192
RESEND_FROM_EMAIL
noreply@example.com
-
config.go:190
RESEND_FROM_NAME
CTF Platform
-
config.go:191
RESEND_VERIFY_TTL_HOURS
24
Email verification token TTL
config.go:193
RESEND_RESET_TTL_HOURS
1
Password reset token TTL
config.go:194
VERIFY_EMAILS
true
If false, registrations skip email verification
config.go:154
OAuth providers
Variable
Vault path / field
Effect when empty
OAUTH_GITHUB_CLIENT_ID
oauth.github_client_id
GitHub login button hidden
OAUTH_GITHUB_CLIENT_SECRET
oauth.github_client_secret
(must match the value registered in GitHub OAuth App)
OAUTH_GOOGLE_CLIENT_ID
oauth.google_client_id
Google login button hidden
OAUTH_GOOGLE_CLIENT_SECRET
oauth.google_client_secret
(must match Google Cloud Console)
Telegram alerts (Alertmanager)
Variable
Read by
Effect when empty
TELEGRAM_BOT_TOKEN
docker-compose.yml:511, alertmanager.yml
setup.sh prompts (y/N) on first start; "no" writes a null-receiver stub
TELEGRAM_CHAT_ID
docker-compose.yml:512, alertmanager.yml
(same)
DEFAULTS - review only if needed
These have sane production defaults. Change only if you know you need to.
Platform identity
Variable
Default
Read by
Notes
APP_NAME
CTF Platform
backend config.go:147
Used in email From-name and elsewhere
APP_VERSION
1.0.0
backend config.go:148
Cosmetic
JWT_ISSUER
ctf-platform
backend config.go:181
iss claim in issued JWTs
VITE_APP_NAME
CTF Platform
frontend Dockerfile ARG
Build-time - baked into SPA navbar/title
Logging
Variable
Default
Notes
LOG_LEVEL
info
Levels: debug, info, warn, error
STRUCTURED_LOGGER
true
JSON log format for Loki ingestion
DEBUG_ENABLED
false
Verbose stack traces
Backend runtime
Variable
Default
Notes
SECURE_COOKIES
true
Forced to true if API_BASE_URL starts with https://
BACKEND_PORT
8090 (.env.example) / 8080 (compose)
Compose forces port 8080 inside the container
MIGRATIONS_PATH
migrations
Goose migrations directory
HTTP_SHUTDOWN_TIMEOUT
15 (seconds)
Graceful shutdown deadline
Competition rules
Variable
Default
Notes
COMPETITION_MODE
flexible
Allowed: solo_only, teams_only, flexible
ALLOW_TEAM_SWITCH
true
If false, users cannot leave/join teams once joined
MIN_TEAM_SIZE
1
Validated at registration
MAX_TEAM_SIZE
10
-
Vault
Variable
Default
Notes
VAULT_ADDR
http://vault:8200
Internal Docker network address
VAULT_PORT
8200
Informational
VAULT_TOKEN
(auto-filled)
setup.sh writes the root token after vault operator init
Postgres
Variable
Default
POSTGRES_USER
admin
POSTGRES_DB
board
POSTGRES_HOST
postgres
POSTGRES_PORT
5432
POSTGRES_SSL_MODE
disable (intra-network only)
POSTGRES_MAX_CONNS
150
POSTGRES_MIN_CONNS
10
Redis
Variable
Default
REDIS_HOST
redis
REDIS_PORT
6379
REDIS_POOL_SIZE
50
REDIS_MIN_IDLE
10
JWT TTLs
Variable
Default
Notes
JWT_ACCESS_TTL_MINUTES
15
Short-lived access token
JWT_REFRESH_TTL_HOURS
72
Long-lived refresh token (httpOnly cookie)
Network / proxy
Variable
Default
Notes
TRUSTED_PROXY_CIDRS
172.16.0.0/12
Docker bridge - backend trusts X-Forwarded-For from these
METRICS_ALLOWED_IPS
""
If empty, /metrics rejects all (set CIDRs to allow Prometheus)
Rate limits
Variable
Default
Notes
RATE_LIMIT_SUBMIT_FLAG
10
Per-user flag submission limit
RATE_LIMIT_SUBMIT_FLAG_DURATION
1
Duration in minutes
Storage
Variable
Default
Notes
STORAGE_PROVIDER
s3
Allowed: filesystem, s3
STORAGE_LOCAL_PATH
./uploads
Used when provider=filesystem
STORAGE_S3_ENDPOINT
seaweedfs:8333
Internal endpoint
STORAGE_S3_BUCKET
ctf
-
STORAGE_S3_REGION
us-east-1
minio-go expects this
STORAGE_S3_USE_SSL
false
Internal network
STORAGE_PRESIGNED_EXPIRY_MINUTES
60
Lifetime of presigned download URLs
Grafana / monitoring
Variable
Default
GRAFANA_ADMIN_USER
admin
GRAFANA_PORT
3000
HAProxy
Variable
Default
Notes
ADMIN_ALLOWED_IPS
RFC-1918 ranges + 127.0.0.1/32
IPs allowed to reach grafana/s3 admin subdomains
HAPROXY_STATS_USER
admin
Basic-auth user for :8405/stats
HAPROXY_BEHIND_CDN
false
If true, HAProxy trusts X-Forwarded-For from TRUSTED_CDN_CIDRS
TRUSTED_CDN_CIDRS
""
Cloudflare / DDoS-Guard CIDR list (used only if HAPROXY_BEHIND_CDN=true)
SeaweedFS ports
Variable
Default
SEAWEED_S3_PORT
8333
SEAWEEDFS_UI_PORT
5000
SEAWEEDFS_UI_IMAGE
ctf-platform-seaweedfs-ui
Docker image tags
Variable
Default
BACKEND_IMAGE
ctf-platform-backend:latest
FRONTEND_IMAGE
ctf-platform-frontend:latest
Vault paths overview
The backend fetches all 8 paths in parallel at startup (config.go:loadFromVault, errgroup.WithContext, 30s timeout). If any path is missing or unreachable, the corresponding env value is used as fallback (graceful degradation).
These are consumed by frontend/board/Dockerfile as ARG directives and baked into the SPA bundle at docker build time (Vite reads them via import.meta.env). To change them you must rebuild and redeploy the frontend container.
Variable
Default
Notes
VITE_APP_NAME
CTF Platform
Browser tab title and navbar brand
VITE_API_BASE_URL
https://api.example.com/api/v1
Production: must point at API subdomain
VITE_WS_URL
wss://api.example.com/api/v1/ws
WebSocket endpoint
VITE_SSE_URL
https://api.example.com/api/v1/sse
SSE fallback endpoint
The SPA also has a runtime fallback (shared/config/env.ts): if VITE_API_BASE_URL is empty in dev, it falls back to /api/v1 (same-origin via Nginx proxy). In production, missing VITE_API_BASE_URL causes the app to throw on startup.
SeaweedFS UI (separate frontend)
The frontend/seaweedfs-ui/ Dockerfile consumes its own set of build args, all unrelated to the main SPA:
Variable
Default
VITE_API_URL
https://api.example.com
VITE_HOST
s3.example.com
VITE_FILER_PORT
8888
VITE_MASTER_PORT
9333
VITE_MASTER_PROXY_PATH
master
VITE_FILER_PROXY_PATH
filer
HAProxy entrypoint environment
deployment/haproxy/entrypoint.sh reads these and generates HAProxy map files / fragment configs:
After successful issuance the entrypoint invokes renewal-hook.sh once to install the cert into HAProxy via Runtime API, then enters a sleep 12h ; certbot renew loop.
What setup.sh writes back to .env
After the wizard completes (or ./setup.sh start initializes Vault for the first time), the following keys are mutated in-place:
Key
Source
DOMAIN
wizard step 2 (and all derived URLs)
VAULT_TOKEN
written from vault operator init JSON output (setup.sh:752–754)
USE_LE_STAGING
wizard step 2 (y/n prompt)
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
wizard step 7 (or do_start prompt if alertmanager.yml missing)
*_DOMAIN, API_BASE_URL, FRONTEND_URL, CORS_ORIGINS, OAUTH_*_REDIRECT_URL, all VITE_* URLs
derived from DOMAIN
POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB
wizard step 3
REDIS_PASSWORD
wizard step 4
SEAWEED_S3_ACCESS_KEY, SEAWEED_S3_SECRET_KEY
wizard step 6
GRAFANA_ADMIN_PASSWORD, HAPROXY_STATS_PASSWORD
wizard steps 7 / auto-gen
OAUTH_GITHUB_CLIENT_ID, OAUTH_GOOGLE_CLIENT_ID
wizard step 8 (public IDs only - secrets go to Vault)
The env_set helper (setup.sh:159) preserves comments and structure when updating values, so manual edits to ordering or comments survive subsequent reconfigure runs.
Secret generators (quick reference)
If you want to supply your own values for the OPTIONAL block: