Type to search.

Environment Variables

Skol Arena has no central env-validation file — every variable below is read directly where it’s needed. This page is the canonical reference.

Database & migrations

Variable Purpose Required Default
DATABASE_URL PostgreSQL connection string. Used by the app’s connection pool, Better Auth, the Drizzle migrator, and the background job queue. Yes
DATABASE_POOL_MAX Maximum connections in the pool. No 10
MIGRATIONS_FOLDER Path to the Drizzle migrations directory. No ./drizzle (preset in the Docker image)

Migrations run automatically, synchronously, at server startup — before any traffic is accepted. There is no manual migrate command. If a migration fails, the process exits immediately (useful for container orchestrators to detect a bad deploy).

Server & runtime

Variable Purpose Required Default
NODE_ENV Standard Node environment flag; affects CORS origin behavior. No dev behavior if unset
PORT Port the server listens on. No 3000
FRONTEND_URL Allowed CORS origin and Better Auth trusted origin for the frontend. No http://localhost:5173
FRONTEND_BUILD_PATH Single-container mode: serves the built frontend (static assets + SPA fallback) from this path. Set to an empty value to serve the API only. No unset outside Docker (preset in the image)
LOG_LEVEL Pino log level. No info
LOG_FORMAT Log output format: json or logfmt. No json
APP_TIMEZONE Timezone used for time-of-day/day-of-week logic in the contextual rules engine. No Europe/Paris
INITIAL_ADMIN_EMAIL Email address for the auto-created super-admin account. Its password is regenerated and logged on every startup until that account logs in once. No admin@skol-arena.local
API_DOCS_ENABLED Serves the OpenAPI documents and the Scalar reference at /api/docs. Set to "false" to switch them off. See API versioning. No on in development, off when NODE_ENV=production
RANKED_MATCH_MAX_AGE_HOURS How long after being played a ranked match can still be declared, in hours. 0 turns the check off — a test/backfill setting, not something to run in production. Invalid values fall back to the default. No 48

Authentication (Better Auth)

Variable Purpose Required Default
BETTER_AUTH_SECRET Signing secret for sessions and cookies. Yes
BETTER_AUTH_URL Better Auth’s own base URL; also added as a trusted origin. No falls back to BASE_URL, then http://localhost:3000
BASE_URL Secondary fallback for the auth base URL. No http://localhost:3000
ENABLE_EMAIL_PASSWORD Shows/hides the email+password login form on the frontend (set to the literal string "false" to hide it). No enabled
KEYCLOAK_CLIENT_ID Keycloak OAuth client ID. No — but all three Keycloak variables are needed together to enable SSO
KEYCLOAK_CLIENT_SECRET Keycloak OAuth client secret. No (see above)
KEYCLOAK_ISSUER Keycloak issuer URL (also used to derive the realm name shown on the login button). No (see above)
KEYCLOAK_PKCE Enables PKCE for the Keycloak OAuth flow (literal "true" to enable). No disabled
KEYCLOAK_LOGIN_LABEL Custom label for the Keycloak login button. No

At least one authentication method must be active: the app refuses to start if ENABLE_EMAIL_PASSWORD is disabled and Keycloak isn’t fully configured.

Email (SMTP)

Used for transactional email such as password resets.

Variable Purpose Required Default
SMTP_HOST SMTP server host. Yes, for email to work
SMTP_PORT SMTP server port. No 587
SMTP_SECURE Use TLS (typically "true" for port 465). No false
SMTP_USER SMTP auth username. Omit for a server without AUTH. No
SMTP_PASSWORD SMTP auth password. No
SMTP_FROM “From” email address. Yes, for email to work
SMTP_FROM_NAME Display name in the “From” header. No

If these are left unset, the app still starts fine — email sending will simply fail when triggered (e.g. a password-reset request). The server checks the SMTP connection at startup and logs a warning when it is unreachable.

With a local mail catcher (maildev, mailpit…), set SMTP_HOST=127.0.0.1 rather than localhost: the latter resolves to ::1 first while those tools bind IPv4 only, which yields connect ECONNREFUSED ::1:<port>.

Web push notifications (VAPID)

Variable Purpose Required Default
VAPID_PUBLIC_KEY VAPID public key for browser push notifications. No — push is silently disabled unless both keys are set
VAPID_PRIVATE_KEY VAPID private key. No (see above)

Real-time (WebSocket)

No dedicated variables. WebSocket connections are mounted on the same server and port as the HTTP API, and authenticated using the existing Better Auth session — nothing extra to configure.