Environment reference¶
Every knob the three apps expose is in the table below. Defaults come from the actual server code, not the sample compose files, so if a README example and this page disagree, this page wins. The last three columns show which app reads which variable: Y means the app respects it, blank means it does not.
Env-lock: what setting a var does to the UI¶
Almost every runtime setting has two homes: an environment variable, and a field inside the Settings UI backed by the app_config table. When a variable is set at container start, the server writes the value into app_config and marks the field as env-locked. Two things follow.
First, the client marks the field read-only (with a small lock badge) and refuses to save changes to it. Any admin who tries to edit an env-locked value gets a "locked by environment variable" error from GET /api/app-config/env-locks. Second, if you later unset the variable and restart the container, the lock clears and the field becomes editable again from the UI.
The pattern applies to SMTP, AI, OIDC providers, backup schedule, and (NutriTrace only) OFF mirror settings. Two implications worth internalising: (1) once you decide to manage a setting from env, keep managing it from env, because someone else's UI save will be silently overridden on the next boot; (2) secrets live only on the server. The browser never receives the raw AI_API_KEY, SMTP_PASS, or OIDC_CLIENT_SECRET; the API returns eight-dot placeholders instead.
Any variable in the tables below can also be provided via Docker secrets by suffixing _FILE. SMTP_PASS_FILE=/run/secrets/smtp_pass, AI_API_KEY_FILE=/run/secrets/openai_key, JWT_SECRET_FILE=/run/secrets/jwt, and so on. Setting both NAME and NAME_FILE is a boot-time error. See Docker secrets for the full pattern.
Core¶
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
PORT |
3001 (CT/NT), 3003 (LT) |
Container listen port. Change if you remap. | Y | Y | Y |
BASE_URL |
(empty) | Subpath mount, e.g. /cooktrace. Must start with /, no trailing slash. Emitted to the browser as window.__<APP>_CONFIG__.basePath. |
Y | Y | Y |
LOG_LEVEL |
info |
error, warn, info, debug. |
Y | Y | Y |
NODE_ENV |
(empty) | Set to production in real deployments. Refuses to boot without JWT_SECRET. |
Y | Y | Y |
Storage¶
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
DB_PATH |
./<app>.db |
SQLite file. In the container, mount to /data/db/<app>.db. WAL mode is enabled at boot. |
Y | Y | Y |
UPLOADS_PATH |
./uploads |
Upload directory. Mount to /data/uploads. Served publicly at /uploads/* (Android WebView can't send Authorization on <img src>). |
Y | Y | Y |
BACKUPS_PATH |
<UPLOADS_PATH>/backups |
Where the built-in "Full Backup" writes ZIPs. | Y | Y | Y |
Auth and crypto¶
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
JWT_SECRET |
insecure dev fallback | Signs the auth cookie and Bearer tokens. Required when NODE_ENV=production. Long random string; never share across apps. |
Y | Y | Y |
TOKEN_ENC_KEY |
HKDF-derived from JWT_SECRET |
AES-GCM key at rest for OIDC client secrets and (NT) wearable OAuth tokens. Set explicitly if you plan to rotate JWT_SECRET. |
Y | Y | Y |
RECOVERY_TOKEN |
unset | Enables POST /api/auth/recover from the login page. Passphrase compare is constant-time. Without this the endpoint is disabled. |
Y | Y | Y |
INSECURE_COOKIES |
unset | Set 1 on plain-HTTP LAN deployments so the Secure attribute comes off the cookie. Otherwise every request after login 401s. |
Y | Y | Y |
MAX_SESSION_HOURS |
8760 (1 year) |
Cap on JWT and cookie lifetime. Per-user overrides may be lower. | Y | Y | Y |
SMTP¶
The whole group is optional. If SMTP_HOST is set, the entire Email tile locks in the admin UI and the DB values are ignored. SMTP_PASS is never returned to the browser; the API sends eight dots instead.
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
SMTP_HOST |
unset | Hostname of your relay. Setting this triggers env-lock for the whole group. | Y | Y | Y |
SMTP_PORT |
587 |
587 for STARTTLS, 465 for implicit TLS. | Y | Y | Y |
SMTP_SECURE |
false |
true for SSL on 465, false (default) for STARTTLS on 587. |
Y | Y | Y |
SMTP_USER |
unset | Login for the relay. | Y | Y | Y |
SMTP_PASS |
unset | Password for the relay. Use SMTP_PASS_FILE in production. |
Y | Y | Y |
SMTP_FROM |
unset | RFC 5322 From header, e.g. "CookTrace" <noreply@example.com>. |
Y | Y | Y |
Trace AI¶
Same envelope across all three apps: pick a provider, hand it a key, optionally pin a model. AI_ENABLED=true auto-enables Trace for every user; leave it unset if you want each user to opt in themselves.
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
AI_ENABLED |
unset | true = auto-enable Trace for all users. |
Y | Y | Y |
AI_PROVIDER |
unset | claude, openai, gemini, oai-compat. oai-compat covers Ollama, LM Studio, vLLM, DeepSeek, Groq, LocalAI. |
Y | Y | Y |
AI_API_KEY |
unset | Required for cloud providers. Optional for oai-compat (Ollama runs keyless). |
Y | Y | Y |
AI_MODEL |
provider default | Optional for cloud, required for oai-compat. |
Y | Y | Y |
AI_BASE_URL |
unset | Required for oai-compat. Reachable from inside the container (Docker network hostnames work). |
Y | Y | Y |
OIDC¶
The shorthand OIDC_* variables alias OIDC_PROVIDER_1_*. For multi-provider setups, add OIDC_PROVIDER_2_*, OIDC_PROVIDER_3_*, and so on. Any provider defined by env shows a lock badge in the admin UI and refuses edits. See OIDC / SSO for full worked examples with Authentik, Pocket-ID, Keycloak, and Google Workspace.
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
OIDC_ISSUER |
unset | Discovery URL root. | Y | Y | Y |
OIDC_CLIENT_ID |
unset | Client ID from your IdP. | Y | Y | Y |
OIDC_CLIENT_SECRET |
unset | Client secret. Encrypted at rest with TOKEN_ENC_KEY. |
Y | Y | Y |
OIDC_DISPLAY_NAME |
unset | Button label on the login page. | Y | Y | Y |
OIDC_LOGO_URL |
unset | Optional icon on the login button. | Y | Y | Y |
OIDC_SCOPE |
openid profile email |
Space-separated scopes. | Y | Y | Y |
OIDC_REDIRECT_URIS |
unset | Comma-separated. Include both the PWA callback and the <app>://oidc-callback deep link if you use Android. |
Y | Y | Y |
OIDC_TOKEN_AUTH_METHOD |
client_secret_post |
Also client_secret_basic or none. |
Y | Y | Y |
OIDC_ADMIN_GROUP_CLAIM |
unset | Claim name that lists groups, e.g. groups. |
Y | Y | Y |
OIDC_ADMIN_GROUP_VALUE |
unset | Membership in this group promotes the user to admin on every sign-in. | Y | Y | Y |
OIDC_AUTO_LINK |
1 |
Link SSO identity to an existing account with the same verified email. | Y | Y | Y |
OIDC_AUTO_REGISTER |
0 |
Create a new account on first SSO sign-in. | Y | Y | Y |
OIDC_IS_ACTIVE |
1 |
Set 0 to disable the provider without removing the config. |
Y | Y | Y |
OIDC_ENABLE_EMAIL_PASSWORD_LOGIN |
1 |
Set 0 for SSO-only mode (password login disabled server-wide). Locks the admin toggle. |
Y | Y | Y |
Backup¶
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
BACKUP_SCHEDULE |
off |
off, daily, weekly, monthly. Setting any of the three backup vars env-locks the tile. |
Y | Y | Y |
BACKUP_TIME |
03:00 |
HH:MM, container timezone. |
Y | Y | Y |
BACKUP_RETENTION |
7 |
Number of backups to keep. Clamped to 1..99. | Y | Y | Y |
BACKUP_UPLOAD_MAX_MB |
512 |
Cap on restore-upload ZIP size (multer). | Y | Y | Y |
App-specific¶
| Variable | Default | Purpose | CT | LT | NT |
|---|---|---|---|---|---|
IMPORT_ZIP_MAX_MB |
512 |
Cap on the bulk recipe-import ZIP. | Y | ||
PYTHON_BIN |
python3 |
Interpreter used by the recipe-scrapers bridge. | Y | ||
EXERCISE_SOURCES |
(empty) | Comma-separated auto-seed IDs: wger, free-db, exercisedb, exercisedb-oss. Empty triggers the wizard picker. |
Y | ||
EXERCISEDB_OSS_URL |
https://oss.exercisedb.dev |
Override the ExerciseDB-OSS mirror. | Y | ||
ALLOW_PRIVATE_RADIO_URLS |
0 |
Set 1 to allow the radio proxy to reach RFC 1918 / loopback / IPv6 ULA (LAN Icecast, Navidrome, Subsonic). |
Y | ||
OFF_LOCAL_DB |
unset | In-container path to the OFF Parquet or DuckDB mirror (e.g. /data/off-mirror/off.parquet). |
Y | ||
OFF_LOCAL_ONLY |
unset | Set 1 for air-gap mode: refuse the public OFF fallback. |
Y | ||
OFF_LOCAL_URL |
HuggingFace food.parquet |
Override the download source used to refresh the mirror. | Y | ||
API_RATE_LIMIT_PER_MIN |
60 |
Federation API Bearer-token rate cap, per token. | Y |