Skip to main content

Environment & secrets

SES keeps non-secret configuration separate from secrets. Both live in env files outside of version control and are supplied to Docker at runtime.

The two env files

FileContentsCommitted to git?
.env.local / .env.prdNon-secret configuration (domain, feature flags)No
.env.secretsAll secrets (passwords, keys, tokens)No

Public URL

VariableExamplePurpose
DOMAINdocs.safeexamsupport.euPublic domain
URL_SCHEMEhttpsPublic URL scheme
PUBLIC_PORT443Public port

Django

VariableDefaultPurpose
DEBUGFalse in productionDebug mode
ENVIRONMENTproductionDeployment environment
ALLOWED_HOSTScomma-separatedDjango allowed hosts
CSRF_TRUSTED_ORIGINScomma-separatedDjango CSRF origins

Database

VariableDefaultPurpose
DB_HOSTodysseyDB host (connection pooler)
DB_PORT6432DB port (pooler)
DB_NAMEses_server_dbDatabase name
DB_USERpostgresDatabase user
POOL_TYPEsessionPool type (session = schema isolation)
POOL_SIZE100Pool size

SSO

VariableDefaultPurpose
SSO_ENABLEDTrueEnable Keycloak SSO
KEYCLOAK_URLhttp://keycloak:8080Internal Keycloak URL
KEYCLOAK_EXTERNAL_URLhttps://kcd.example.comPublic Keycloak URL
KEYCLOAK_REALMsesKeycloak realm
KEYCLOAK_CLIENT_IDses-serverOIDC client ID

Rate limiting

VariableDefaultPurpose
RATE_LIMIT_GLOBAL50000Global requests / minute
RATE_LIMIT_INSTITUTE5000Requests / minute per institute

Generating secrets

# In the deployment repository
chmod +x generate/generate_secrets.sh
./generate/generate_secrets.sh <your-github-pat>

The script is idempotent — it keeps existing values and only fills in missing ones.

Keep secrets private

Never commit .env.secrets. Use .env.secrets.example to document which variables exist, without exposing values.

Session token must match across the stack

SES_SESSION_TOKEN_SECRET must be identical on the server and the proxy container, otherwise the proxy rejects valid sessions.