Environment variables¶
The server reads configs/config.yaml and then overrides it with environment variables.
Naming rule¶
Config key a.b.c → environment variable NULLUS_A_B_C.
Keys missing from the config file cannot be overridden by environment variables alone
Automatic mapping looks only at keys present in the config file. Keys that are not there must be bound separately in code; otherwise, setting the environment variable is silently ignored — the backup sealing key once fell into this trap and blocked startup.
Keys bound separately: platform.namespace · backup.seal_key ·
backup.destination.secret_key · backup.keycloak_database.password ·
server.rate_limit.*.
Server and database¶
| Environment variable | Default |
|---|---|
NULLUS_SERVER_PORT |
8090 |
NULLUS_SERVER_MODE |
development |
NULLUS_SERVER_RATE_LIMIT_AUTHENTICATED |
0 → 300 per user per minute |
NULLUS_SERVER_RATE_LIMIT_UNAUTHENTICATED |
0 → 30 per IP per minute (in development, the same as the user limit — an explicit value wins) |
NULLUS_SERVER_RATE_LIMIT_IP_CEILING |
0 → 600 per IP per minute (raising the user or anonymous limit lifts it too — an explicit value wins) |
NULLUS_DATABASE_HOST |
localhost |
NULLUS_DATABASE_PORT |
5433 |
NULLUS_DATABASE_NAME |
nullus |
NULLUS_DATABASE_USER |
nullus |
NULLUS_DATABASE_PASSWORD |
nullus_dev |
NULLUS_DATABASE_SSLMODE |
disable |
NULLUS_DATABASE_MAX_OPEN_CONNS / _MAX_IDLE_CONNS |
25 / 10 |
NULLUS_DATABASE_CONN_MAX_LIFETIME / _CONN_MAX_IDLE_TIME |
5m / 1m |
Requests over a limit are rejected with 429. Zero or less means the default. Automation that opens many
screens at once from one IP (e2e, for example) can pass 300 per minute even in development mode, so CI starts
the e2e API with higher limits (AUTHENTICATED=6000 · IP_CEILING=12000). Use the same values when you run
Playwright locally. If an explicit IP ceiling is below the user limit, a warning is logged at startup. The login
and deploy limits (10 per minute) cannot be changed. The limiter records every request time, so do not set the
limits higher than you need.
The old NULLUS_DB_* names do not work
Abbreviated names such as NULLUS_DB_HOST match no config key and are ignored.
.env.example, .env.dev, and the Makefile now use the correct names, but if
you copied a .env earlier, you need to update that file.
Authentication¶
| Environment variable | Default | Notes |
|---|---|---|
NULLUS_AUTH_MODE |
session | oidc or anything else. Use oidc in production |
NULLUS_AUTH_SESSION_SECRET |
dev-secret-change-in-production | HMAC key for issuing local tokens. If empty, the /auth/login route is not registered |
NULLUS_AUTH_SESSION_MAX_AGE |
86400 | A value of 0 or less falls back to 24 hours |
NULLUS_AUTH_OIDC_PROVIDER |
keycloak | |
NULLUS_AUTH_OIDC_ISSUER_URL |
http://localhost:8180/realms/nullus |
If empty in oidc mode, the server refuses to start |
NULLUS_AUTH_OIDC_AUDIENCE |
nullus-app |
Keycloak (variables with aliases)¶
| Environment variable | Alias | Default |
|---|---|---|
NULLUS_KEYCLOAK_ADMIN_URL |
KEYCLOAK_URL |
Empty — leaving it empty skips the entire OSS SSO integration |
NULLUS_KEYCLOAK_PUBLIC_URL |
KEYCLOAK_PUBLIC_URL |
If empty, inherits the issuer |
NULLUS_KEYCLOAK_REALM |
KEYCLOAK_REALM |
nullus |
NULLUS_KEYCLOAK_ADMIN_USER |
KEYCLOAK_ADMIN_USER |
admin |
NULLUS_KEYCLOAK_ADMIN_PASSWORD |
KEYCLOAK_ADMIN_PASSWORD |
Empty |
The first (prefixed) name takes precedence.
Other config keys¶
| Environment variable | Default |
|---|---|
NULLUS_HELM_TIMEOUT / NULLUS_HELM_NAMESPACE_PREFIX |
600s / nullus- |
NULLUS_PROMETHEUS_URL |
Empty |
NULLUS_LOG_LEVEL / NULLUS_LOG_FORMAT |
debug / text |
NULLUS_PLATFORM_NAMESPACE |
Injected by the chart |
NULLUS_BACKUP_* |
See the backup docs |
Variables that do not follow the prefix rule¶
These bypass the config loader; the code reads them directly.
| Environment variable | What it does | Default |
|---|---|---|
ENCRYPTION_KEY |
Encrypts and decrypts kubeconfigs and tokens. Exactly 32 bytes | None — runtime 500 if empty |
OPENBAO_ADDR / OPENBAO_TOKEN |
Global OpenBao, local only | Registered only when both are set |
NULLUS_GITLAB_URL / NULLUS_GITEA_URL / NULLUS_JENKINS_URL |
Override tool addresses | Empty |
NULLUS_HELM_OCI_REGISTRY |
Air-gapped OCI registry. The installed-image scan also pulls images from this host (the same registry the nodes' containerd mirrors use) | Empty |
NULLUS_TELEGRAM_API_URL |
For notification tests | Empty |
NULLUS_STACK_ALERT_INTERVAL |
Alert collection interval | 1m (off or 0 turns it off) |
TOKEN_ROTATION_INTERVAL |
Token rotation check interval | 5m |
CICD_RUN_SYNC_INTERVAL |
CI run sync interval | 10m |
STACK_IMAGE_RESCAN_INTERVAL |
Installed-image rescan interval (air-gapped installs included). Newly found fixable CRITICALs go out as the stack_cve_found notification |
24h |
NULLUS_SCM_GROUP |
GitLab group for pipelines | nullus |
NULLUS_DEFAULT_ORG_ID |
Default organization ID | Fixed UUID in code |
CLI and MCP only¶
NULLUS_SERVER · NULLUS_TOKEN · NULLUS_CONFIG_DIR ·
NULLUS_MCP_ALLOW_WRITE · NULLUS_SCM_TOKEN
nullus-bootstrap only¶
KEYCLOAK_URL(required) · KEYCLOAK_REALM(nullus) · KEYCLOAK_ADMIN_USER(admin) ·
KEYCLOAK_ADMIN_PASSWORD · BOOTSTRAP_CLIENT_ID(nullus-bootstrap) ·
BOOTSTRAP_ROLE(admin)
token-source-sync only¶
DATABASE_URL · OPENBAO_ADDR(required) · OPENBAO_TOKEN(required) ·
TOKEN_SOURCE_ENV(default dev)
The config file path cannot be changed¶
The server reads configs/config.yaml from a hard-coded path. There is no flag or
environment variable to change it. In containers, the chart mounts the ConfigMap at
the right location to match.
configs/config.dev.yaml is not referenced anywhere — not by the Makefile, scripts,
workflows, or code (it appears to be an orphan file).