Configuration (env)¶
All configuration arrives through environment variables. The backend uses the
platform-core config model; the sample file is
backend/internal/config/.env.example.
Never commit secrets
backend/internal/config/.env*, the root .env and backend.env are
gitignored. When you add a variable, document its name in the README
and here — never its value.
Core¶
| Variable | Example | Notes |
|---|---|---|
PORT |
8080 |
API listen port |
ENVIRONMENT |
production |
Enables strict mode |
DEBUG |
false |
Verbose logging |
ALLOWED_ORIGINS |
https://geregeapp.mn |
CORS allow-list (comma separated; * only in dev) |
TRUSTED_PROXIES |
10.0.0.0/8 |
IP ranges whose X-Forwarded-For is trusted |
With TRUSTED_PROXIES empty
X-Forwarded-For is not trusted at all (fail-safe). Behind a reverse
proxy you must set it, or rate limiting will treat every user as one IP.
Database and Redis¶
| Variable | Notes |
|---|---|
DB_POSTGRE_DSN / DB_POSTGRE_URL |
Connection string |
DB_POSTGRE_DRIVER |
Driver name |
DB_MAX_OPEN_CONNS · DB_MAX_IDLE_CONNS · DB_CONN_MAX_LIFE_MINS |
Pool |
REDIS_HOST · REDIS_PASS · REDIS_EXPIRED |
Redis and TTL |
Two hard requirements in production
- The DSN must use
sslmode=verify-full. - The API must connect as a non-superuser role — otherwise RLS does not apply and the boot guard refuses to start.
The compose stack deliberately runs ENVIRONMENT=development: the internal
database has no TLS.
JWT and session¶
| Variable | Notes |
|---|---|
JWT_SECRET |
≥32 characters. Changing it invalidates every session |
JWT_EXPIRED · JWT_REFRESH_EXPIRED |
Access / refresh lifetimes |
JWT_ISSUER |
Usually the app domain. Changing it invalidates all tokens |
BCRYPT_COST |
Hash cost (no passwords, but used for internal hashes) |
OTP_MAX_ATTEMPTS |
OTP attempts during super-admin onboarding |
eID (relying party)¶
| Variable | Notes |
|---|---|
EID_BASE_URL |
eID Mongolia /v3 base |
EID_RP_UUID · EID_RP_SECRET |
RP credentials |
EID_RP_NAME |
Display name of the RP |
EID_CALLBACK_URL |
Callback address |
EID_CERT_LEVEL |
Required certificate level |
EID_DISPLAY_TEXT |
Text shown on the phone |
Signing¶
| Variable | Notes |
|---|---|
SIGN_SIGNER_CERT_FILE · SIGN_SIGNER_KEY_FILE |
Required in production — the persistent Document-Signer |
SIGN_RELAY_TOKEN |
Sign-relay token for third-party RPs (empty = disabled) |
Gerege SSO — client side¶
Settings for this app acting as a relying party of sso.gerege.mn:
| Variable | Example | Notes |
|---|---|---|
SSO_ISSUER |
https://sso.gerege.mn |
Defaults to this when empty |
SSO_CLIENT_ID · SSO_CLIENT_SECRET |
— | Empty disables the SSO flow |
SSO_REDIRECT_URI |
https://geregeapp.mn/sso/callback |
Must match the registration exactly |
SSO_SCOPE |
openid profile email |
Add nationalid to receive the national ID |
SSO_NATIVE_CLIENT_ID |
— | Mobile (PKCE, public) client |
SSO_EID_PROXY_BASE_URL |
https://sso.gerege.mn/rp/eid |
Fetch the eID console through the SSO proxy |
OIDC provider — issuer side¶
| Variable | Notes |
|---|---|
OAUTH_ISSUER |
e.g. https://geregeapp.mn. The provider mounts only when set |
SSO_STATE_KEY |
HMAC key for login/consent state (≥32 bytes) |
SSO_FIRSTPARTY_CLIENTS |
Clients that skip consent |
SSO_ADMIN_API_KEYS · SSO_ADMIN_SUBS |
Access to /admin/api/v1 |
AI (Gemini)¶
| Variable | Notes |
|---|---|
GEMINI_API_KEY |
Without it /ai/* returns a genuine 500 |
GEMINI_MODEL |
Chat / STT / translation (gemini-2.5-flash) |
GEMINI_TTS_MODEL |
TTS model |
GEMINI_EMBED_MODEL |
Empty = the client auto-selects |
GEMINI_VOICE |
Prebuilt voice (Kore) |
GEMINI_API_BASE |
Override for proxies / tests |
AI_SCOPE_PROMPT |
Scope fallback when the DB layer is empty |
Third parties and storage¶
| Variable | Notes |
|---|---|
GOOGLE_CLIENT_ID · GOOGLE_CLIENT_SECRET |
Google linking (empty hides the button) |
INTEGRATION_ENC_KEY |
≥16 bytes. Encrypts OAuth tokens and super-admin TOTP |
VERIFY_API_BASE · VERIFY_API_KEY · VERIFY_CHANNEL |
Gerege Verify |
VERIFY_OPTIONAL |
true lets flows continue without Verify |
XYP_API_BASE · XYP_CLIENT_ID · XYP_CLIENT_SECRET |
State registry lookups |
CORE_API_BASE · CORE_API_TOKEN |
Gerege Core (USER FIND / ORG FIND) |
GSPACE_HOST · GSPACE_PORT · GSPACE_USER · GSPACE_PASSWORD |
SFTP |
GSPACE_HOST_KEY |
Host key pin — protects against MITM |
GSPACE_BASE_PATH · GSPACE_QUOTA_BYTES |
Directory, per-user quota |
Never rotate INTEGRATION_ENC_KEY once set
It encrypts super-admin TOTP secrets and integration OAuth tokens. Rotating it breaks both, irrecoverably.
Miscellaneous¶
| Variable | Notes |
|---|---|
SUPERADMIN_EMAIL |
Bootstraps the first super admin |
RELAY_DEMO_MODE |
Relay simulator (generates demo responses) |
Observability¶
| Variable | Notes |
|---|---|
OTEL_EXPORTER |
otlp · stdout · empty |
OTEL_SAMPLE_RATIO |
Trace sampling |
OBSERVABILITY_TOKEN |
Bearer gating /metrics and /swagger in production |
Frontend¶
| Variable | Notes |
|---|---|
BACKEND_URL |
The internal address the BFF calls |
The name api can collide on a shared network
With several stacks on one Docker network, http://api:8080 may resolve to
a different container and every /api/v1/* turns into a 404. Pin
BACKEND_URL to the full container name (e.g.
http://gerege-app-mn-api-1:8080).
Configuration guards¶
These run at boot:
| Guard | What it checks |
|---|---|
| RLS enforceability | In production the DB role is not a superuser / BYPASSRLS |
| TLS | In production the DSN uses sslmode=verify-full |
| Key lengths | JWT_SECRET ≥32, SSO_STATE_KEY ≥32, INTEGRATION_ENC_KEY ≥16 |
| Document-Signer | Certificate and key present in production |
| CORS | * forbidden in production |
Any violation makes the API refuse to start — it never quietly degrades.