# Environment Variables Complete reference for all environment variables TREK reads. ## How to Set Variables - **Docker Compose** — use the `environment:` block or a `.env` file alongside `docker-compose.yml` - **Docker run** — pass each variable with `-e VARIABLE=value` - **Helm** — use `env:` for plain values and `secretEnv:` for sensitive values in `values.yaml`. The chart only passes through the keys it declares (37 in `templates/configmap.yaml`, the credentials in `templates/secret.yaml`), so a variable that is not one of them (`DEFAULT_LANGUAGE`, `FILE_UPLOAD_LIMIT_MB`, `RESTORE_FROM_BACKUP` and `MCP_MAX_SESSION_PER_USER` among them) is dropped silently. Patch it onto the Deployment or add it to the chart - **Unraid** — set in the container template editor - **Proxmox Community Script** — set in `/opt/trek/server/.env` --- ## Startup Validation TREK checks almost all of this surface once at boot. A variable that is **unset or blank** falls back to its documented default; a variable that is **present but malformed** aborts startup, with an aggregated report naming every offending value: ``` Invalid environment configuration: - PORT="not-a-port": must be a port number (1-65535) - SESSION_DURATION="bogus": must be a duration like "1h", "7d" or "30d" ``` The value of a secret is never printed: `ENCRYPTION_KEY`, `SMTP_PASS`, `OIDC_CLIENT_SECRET`, `VAPID_PRIVATE_KEY` and the other keys, secrets and passwords appear as `***`, so the report names the variable and the problem only. In Docker this crash-loops the container until the value is corrected or removed. Boolean switches accept `true`/`false`, `1`/`0`, `on`/`off`, `yes`/`no` in any casing — anything else counts as malformed. Variables TREK does not know are passed through untouched. `TREK_DB_JOURNAL_MODE` and `TREK_DB_SYNCHRONOUS` are exceptions: they log a warning and fall back instead of aborting, because `reset-admin.js` — the way back into a locked-out instance — reads the same two variables and has to keep working. The three plugin caps `TREK_PLUGIN_AI_PER_DAY`, `TREK_PLUGIN_NOTIFY_PER_DAY` and `TREK_PLUGIN_AUDIT_MAX_ROWS` are read by the plugin host rather than by the boot schema, so a malformed value there falls back to the default without a warning. `NODE_ENV` and `TZ` are not validated at all, so a non-standard value like `NODE_ENV=staging` still boots. Spaces around `NODE_ENV`, `LOG_LEVEL` and `DEFAULT_LANGUAGE` are trimmed at startup with a warning in the log; the web UI is only served with `NODE_ENV=production` in lowercase. --- ## Core | Variable | Description | Default | |-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------| | `PORT` | Server port | Sources: `3001`, Docker: `3000` | | `HOST` | Bind address for the HTTP server (e.g. `127.0.0.1`, `10.0.0.72`). **Source / Proxmox installs only** — do not set this in Docker or any containerized deployment. See note below. | all interfaces | | `NODE_ENV` | Environment (`production` / `development`) | `production` | | `ENCRYPTION_KEY` | At-rest encryption key — see resolution order below | auto | | `TZ` | Timezone for logs, reminders, and cron jobs (e.g. `Europe/Berlin`) | `UTC` | | `LOG_LEVEL` | `info` = concise user actions; `debug` = verbose details | `info` | | `DEFAULT_LANGUAGE` | Default language on the login page — see supported codes below | `en` | | `SESSION_DURATION` | How long a login session stays valid before re-login is required. Used when **"Remember me" is unchecked** on the login form (the default): applies to the `trek_session` JWT `exp` claim, and the cookie is issued as a **browser-session cookie** (no `maxAge`, cleared when the browser closes). Accepts `ms`-style strings: `1h`, `12h`, `7d`, `30d`, `90d`. Invalid values abort startup — see Startup Validation above. Does not affect the short-lived MFA challenge token or MCP OAuth tokens (those keep their own TTL). | `24h` | | `SESSION_DURATION_REMEMBER` | Session length used when the user **ticks "Remember me"** on login: a longer-lived JWT `exp` claim plus a **persistent** `trek_session` cookie whose `maxAge` matches, so the session survives browser restarts. Same `ms`-style format and the same startup validation as `SESSION_DURATION`. Also the lifetime of every SSO session when `OIDC_ONLY=true`, where the login page has no such switch. | `30d` | | `ALLOWED_ORIGINS` | Comma-separated origins for CORS and email notification links | same-origin | | `ALLOW_INTERNAL_NETWORK` | Allow outbound requests to private/RFC-1918 IPs. Set `true` if Immich, a document store bound through [Document-Sync](Document-Sync) or other integrated services are on your local network. Loopback (`127.x`) and link-local (`169.254.x`) addresses remain blocked regardless, apart from a link-local address listed in `ALLOW_LINK_LOCAL_IPS`. | `false` | | `ALLOW_LINK_LOCAL_IPS` | Single link-local addresses (`169.254.x.x`) TREK may reach after all, comma-separated. Meant for the host gateway of a rootless Podman container, `169.254.1.2`, with an identity provider or another service behind it. OIDC, AI Parsing, plugin OAuth and your own routing engines reach a listed address directly; the other integrations treat it as internal, so they also need `ALLOW_INTERNAL_NETWORK=true`. `169.254.169.x` and `169.254.170.x`, where cloud providers serve metadata and credentials, cannot be listed, and an entry that is not a single link-local IPv4 aborts startup. See [Internal-Network-Access](Internal-Network-Access#a-link-local-address-you-need). | unset | | `APP_URL` | Public base URL (e.g. `https://trek.example.com`). Required when OIDC is enabled — must match the redirect URI registered with your IdP. Also used as the base URL for email notification links and subscribable calendar feed URLs (the `webcal://`/`https://` links the Subscribe dialog hands to Google/Apple/Outlook). | — | | `TREK_WIKI_DIR` | Where the in-app Help pages (`/help`) read their content from. TREK ships this wiki and serves it from disk, so the docs always match the version you are running. You should not need to set this — it is an escape hatch for unusual layouts. If the directory cannot be found, Help falls back to fetching the repository's `wiki/` folder from the `main` branch on GitHub (which can be ahead of the release you are running, and needs outbound network access). | the bundled `wiki/` directory | ### `HOST` — Source and Proxmox installs only By default TREK binds to all network interfaces (`0.0.0.0`), which is the correct behaviour inside a container because Docker handles port exposure at the host level. Setting `HOST` overrides the bind address at the Node.js level. **When to use it:** only when running TREK directly on a host (git sources or the [Proxmox community script](Install-Proxmox)) and you need to restrict which interface the server listens on — for example, to expose TREK only on a LAN interface while keeping it off the public-facing one. **Never set `HOST` in Docker, Docker Compose, Helm, or Unraid deployments.** Use Docker's `-p ::` syntax or your orchestrator's port binding instead. ``` # .env — source / Proxmox installs only HOST=10.0.0.72 # bind only on this LAN interface PORT=3001 ``` When `HOST` is set, the startup banner includes a `Host:` line confirming the bound address. ### `ENCRYPTION_KEY` — Resolution Order `server/src/config.ts` resolves the encryption key in this order: 1. **`ENCRYPTION_KEY` env var** — explicit value, always takes priority. Persisted to `data/.encryption_key` automatically. 2. **`data/.encryption_key` file** — present on any install that has started at least once. 3. **`data/.jwt_secret` file** — one-time fallback for existing installs upgrading without a pre-set key. The value is immediately persisted to `data/.encryption_key` so JWT rotation cannot break decryption later. 4. **Auto-generated** — fresh install with none of the above; persisted to `data/.encryption_key`. Setting `ENCRYPTION_KEY` explicitly is recommended so you can back it up independently of the data volume. ### `DEFAULT_LANGUAGE` — Supported Codes You can set `DEFAULT_LANGUAGE` to any of the 27 languages TREK ships. The currently supported codes are: | Code | Language | |---------|--------------------| | `en` | English | | `de` | Deutsch | | `es` | Español | | `et` | Eesti | | `fr` | Français | | `hu` | Magyar | | `nl` | Nederlands | | `br` | Português (Brasil) | | `cs` | Česky | | `sk` | Slovenčina | | `pl` | Polski | | `ru` | Русский | | `zh` | 简体中文 | | `zh-TW` | 繁體中文 | | `it` | Italiano | | `tr` | Türkçe | | `ar` | العربية | | `az` | Azərbaycanca | | `id` | Bahasa Indonesia | | `ja` | 日本語 | | `ko` | 한국어 | | `th` | ไทย | | `uk` | Українська | | `gr` | Ελληνικά | | `sv` | Svenska | | `vi` | Tiếng Việt | | `ca` | Català | If you set a code that isn't on this list, TREK refuses to start and prints `DEFAULT_LANGUAGE="…": must be one of: …`. Leave the variable unset to use English (`en`). This list grows as new translations are added to TREK. --- ## Outbound HTTP(S) Proxy TREK can route supported outbound HTTP(S) requests through a proxy by setting the standard variables below. Outbound proxying is disabled by default. | Variable | Description | Default | |---------------|---------------------------------------------------------------|---------| | `HTTP_PROXY` | Proxy URL for outbound HTTP requests | — | | `HTTPS_PROXY` | Proxy URL for outbound HTTPS requests | — | | `NO_PROXY` | Comma-separated hosts or domains that should bypass the proxy | — | > **Note:** Requests that go through TREK's SSRF protection (place photos, link previews, webhooks, Immich, Synology, > Dawarich and the other integrations) read the same three variables themselves and use the proxy as well, on any > install. Everything else follows Node's own environment proxy, which needs `NODE_USE_ENV_PROXY=1` (see below). > `NO_PROXY` takes `*`, a host (which also matches its subdomains, a leading dot is optional) or `host:port`. > **Container only.** Node ignores these variables unless it is started with `NODE_USE_ENV_PROXY=1`, and the official > image sets that for you. On a source or Proxmox install, set `NODE_USE_ENV_PROXY=1` alongside them or nothing will > change. On Helm the image already has it, and the chart passes `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` from > `env:` through to the container. > **Set `NO_PROXY`.** Without it every request goes to the proxy, including the ones TREK makes to itself, such as the > container health check. `localhost,127.0.0.1` is a sensible minimum; add your own hosts as needed. --- ## HTTPS / Reverse Proxy These three variables work together behind a TLS-terminating reverse proxy. See [Reverse-Proxy](Reverse-Proxy) for the full explanation. | Variable | Description | Default | |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------| | `FORCE_HTTPS` | When `true`: 301-redirects HTTP→HTTPS, sends HSTS (`max-age=31536000`), adds CSP `upgrade-insecure-requests`, forces cookie `secure` flag. Only useful behind a TLS proxy. Your proxy must send `X-Forwarded-Proto: https`. | `false` | | `HSTS_INCLUDE_SUBDOMAINS` | When `true`: adds the `includeSubDomains` directive to the HSTS header, extending HTTPS enforcement to all subdomains. Only effective when HSTS is active (`FORCE_HTTPS=true` or `NODE_ENV=production`). Leave `false` if you run other services on sibling subdomains over plain HTTP. | `false` | | `TRUST_PROXY` | Number of trusted proxy hops. Tells Express how far into `X-Forwarded-For` to look for the real client IP, and where to read `X-Forwarded-Proto`. Count your hops: with two proxies in front of TREK and `TRUST_PROXY=1`, the IP on every audit row is the inner proxy's. `0` trusts nothing and always uses the socket address. Not required for the `FORCE_HTTPS` redirect, which reads the `X-Forwarded-Proto` header directly — set it for correct client IPs in the audit log, and for the `COOKIE_SECURE` auto-derivation, which goes through `req.secure`. | `1` (production) | | `HTTP_KEEP_ALIVE_TIMEOUT_MS` | How long (in milliseconds) the server keeps an idle keep-alive connection open; the headers timeout follows one second above it. Keep it above your reverse proxy's upstream idle timeout (60 s for nginx and most load balancers, 90 s for Traefik), otherwise the proxy reuses a connection the server just closed and answers that request with a 502. Between `1000` and `290000`. | `95000` | | `COOKIE_SECURE` | Controls the `secure` flag on the `trek_session` cookie. Auto-derived as `true` when `NODE_ENV=production`, when `FORCE_HTTPS=true`, or when the request itself arrived over TLS on the outermost hop (`X-Forwarded-Proto: https` with `TRUST_PROXY` set). Set to `false` only as an escape hatch for LAN testing without TLS — not recommended in production. | auto | > **Warning:** `FORCE_HTTPS=true` behind a proxy that does not forward `X-Forwarded-Proto: https` causes a > redirect loop — every request looks like plain HTTP to TREK and gets 301'd again. Fix the proxy to send the > header. Setting `TRUST_PROXY` does not help: the redirect accepts the raw `X-Forwarded-Proto: https` header on > its own, whatever `trust proxy` is set to. (`TRUST_PROXY` only feeds the second half of the check, `req.secure`, > which additionally covers a proxy chain that sends a comma-joined `X-Forwarded-Proto` — and production already > trusts one hop by default.) --- ## OIDC / SSO For setup instructions, see [OIDC-SSO](OIDC-SSO). | Variable | Description | Default | |----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------| | `OIDC_ISSUER` | OpenID Connect provider URL (e.g. `https://auth.example.com`) | — | | `OIDC_CLIENT_ID` | OIDC client ID | — | | `OIDC_CLIENT_SECRET` | OIDC client secret | — | | `OIDC_DISPLAY_NAME` | Label shown on the SSO login button | `SSO` | | `OIDC_ONLY` | Force SSO-only mode: disables password login and registration, overrides Admin > Settings toggles, cannot be changed at runtime. First SSO login becomes admin on a fresh instance. Since the login page then has no "Remember me" switch, every SSO session gets the `SESSION_DURATION_REMEMBER` lifetime; shorten that variable for shorter sessions, `SESSION_DURATION` does not apply here. | `false` | | `OIDC_ADMIN_CLAIM` | OIDC claim inspected for the admin role. Only takes effect once `OIDC_ADMIN_VALUE` is set. | `groups` | | `OIDC_ADMIN_VALUE` | Value of the OIDC claim that grants admin role (e.g. `app-trek-admins`) | — | | `OIDC_USERNAME_CLAIM` | Claim a new account's username is built from, e.g. `preferred_username` when your provider sends the full name in `name`. Only used when the account is created; an empty claim falls back to the default order. | `name`, then `preferred_username` | | `OIDC_SCOPE` | Space-separated OIDC scopes to request. **Fully replaces** the default — always include `openid email profile` plus any extra scopes (e.g. add `groups` when using `OIDC_ADMIN_CLAIM`) | `openid email profile` | | `OIDC_DISCOVERY_URL` | Override the auto-constructed OIDC discovery endpoint. Required for providers with a non-standard path (e.g. Authentik) | — | --- ## WebAuthn / Passkeys Passkey (WebAuthn) login is configured from the Admin panel, but the two cryptographically sensitive values can be pinned via environment variables. Env vars take priority over the corresponding database settings. These values are **only** ever derived from server-side config — never from request `Host` / `X-Forwarded-Host` headers (mirroring OIDC redirect-URI handling). | Variable | Description | Default | |--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------| | `WEBAUTHN_RP_ID` | Relying-Party ID — the registrable domain passkeys are bound to (e.g. `trek.example.com`). Overrides the `webauthn_rp_id` DB setting. When unset, it is derived from the hostname of `APP_URL`. Bare IP literals (IPv4/IPv6) are rejected. If it cannot be resolved, passkeys are disabled. | derived from `APP_URL` | | `WEBAUTHN_ORIGINS` | Comma-separated list of allowed origins for passkey ceremonies (e.g. `https://trek.example.com`). Overrides the `webauthn_origins` DB setting; trailing slashes are stripped. When unset and the RP ID is not `localhost`, a single origin is derived from `APP_URL`. In dev (RP ID `localhost`) `http://localhost:5173` and `http://localhost:3001` are added automatically. | derived from `APP_URL` | --- ## Email / SMTP SMTP settings can be configured via the Admin panel or overridden with environment variables. Env vars take priority over the database values. | Variable | Description | Default | |------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|---------| | `SMTP_HOST` | SMTP server hostname (e.g. `smtp.example.com`) | — | | `SMTP_PORT` | SMTP server port. Port `465` enables implicit TLS (`secure: true`); all other ports use STARTTLS or plain. | — | | `SMTP_USER` | SMTP authentication username | — | | `SMTP_PASS` | SMTP authentication password | — | | `SMTP_FROM` | Sender address for outbound emails (e.g. `TREK `) | — | | `SMTP_SKIP_TLS_VERIFY` | Set `true` to disable TLS certificate validation. Useful for self-signed certs on internal SMTP relays — not recommended in production. | `false` | `SMTP_HOST`, `SMTP_PORT`, and `SMTP_FROM` are all required for email delivery to work. `SMTP_USER` and `SMTP_PASS` are optional (for unauthenticated relays). --- ## Web Push [Web Push](Notifications#web-push) signs every message with a VAPID key pair. None of these variables is needed: without them TREK generates a pair on first start and keeps it in the database, the private half encrypted with `ENCRYPTION_KEY`, so it moves with every backup and survives a restore. | Variable | Description | Default | |---------------------|------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| | `VAPID_PUBLIC_KEY` | Public half of your own key pair: the uncompressed P-256 point, base64url (65 bytes). Only used together with `VAPID_PRIVATE_KEY`. | generated | | `VAPID_PRIVATE_KEY` | Private half of that pair: the 32-byte P-256 key, base64url. | generated | | `VAPID_SUBJECT` | The contact the push services see with every message: a `mailto:` address or an `https://` URL. | `APP_URL` when it is `https://`, else `https://github.com/liketrek/TREK` | A value in the wrong shape aborts startup like any other variable on this page; for `VAPID_PRIVATE_KEY` the report prints `***` instead of the value. A pair whose halves do not belong together, or only one half of a pair, does not stop TREK from starting, but push is off with an error in the log until the variables are fixed: the Push card and column disappear from **Settings → Notifications**, the routes that register a device answer `503`, nothing is sent, and every registered device stays registered. TREK never signs with another pair in its place, neither one stored in the database (from a start without the variables) nor a newly generated one: no device registered with yours holds that key, and the first message sent with it would drop every one of them. Once both variables hold the two halves of your pair again, every device receives as before. Any tool that prints the standard Web Push pair works, for example `npx web-push generate-vapid-keys`. Without `VAPID_SUBJECT`, the contact is `APP_URL` when that is an `https://` address other than `localhost`, and otherwise the project page. Unlike links in emails, it never falls back on the first `ALLOWED_ORIGINS` entry, which may be another site that is only allowed to call the API. TREK never fills in an email address on its own, so no admin address travels to the push services. TREK generates a pair only while neither `web_push_vapid_public_key` nor `web_push_vapid_private_key` exists in `app_settings`, and never overwrites a pair that is there. When no pair comes from the variables and the stored private key cannot be decrypted with the current `ENCRYPTION_KEY` (after a restore under a different key, for example), push is off: the log says why, the Push card and column disappear from every user's **Settings → Notifications** (the routes that register a device answer `503`), nothing is sent, and every registered device stays registered. Starting TREK with the original `ENCRYPTION_KEY` again brings push back on every device as it was. A stored private key that does not belong to the stored public key, or only one of the two rows, turns push off the same way. To start over with a new pair instead, delete both rows from `app_settings`; TREK then generates a new pair, and every device has to subscribe again. Browsers bind each subscription to the public key it was made with. After a switch to another pair (setting the variables on an instance that has been using its generated pair, changing them, removing them again, or a newly generated pair), TREK drops each old subscription the first time it would have sent to it. A device subscribes again with the new key on its own the next time TREK is opened on it, where the browser allows that without a tap; elsewhere, push shows as off in **Settings → Notifications** and has to be turned on again there. On Helm, `VAPID_PUBLIC_KEY` and `VAPID_SUBJECT` go under `env:` and `VAPID_PRIVATE_KEY` under `secretEnv:`. --- ## Initial Setup These variables only take effect on first boot, before any user exists. | Variable | Description | Default | |------------------|--------------------------------------|--------------------| | `ADMIN_EMAIL` | Email for the first admin account | `admin@trek.local` | | `ADMIN_PASSWORD` | Password for the first admin account | random | Both variables must be set together. If either is omitted, the account is created with email `admin@trek.local` and a randomly generated password that is printed to the server log. Once any user exists, these variables have no effect. --- ## MCP For setup instructions, see [MCP-Overview](MCP-Overview). | Variable | Description | Default | |----------------------------|---------------------------------------------------------------------------------------------------------|---------| | `MCP_RATE_LIMIT` | Max MCP API requests per minute, counted **per user and per OAuth client** — a user connected from two MCP clients gets this budget twice. Requests made with a static `trek_` token or a session JWT carry no client and share one further bucket per user. | `300` | | `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user. At the cap the user's least-recently-active session is closed to make room — requests are not rejected. | `20` | | `MCP_SESSION_TTL` | Session idle timeout in seconds (max 86400) | `3600` | | `MCP_SSE_KEEPALIVE` | SSE keep-alive ping interval in seconds — keeps the stream alive through reverse proxies. `0` disables the pings; an open stream still refreshes the session's idle timeout. | `25` | --- ## API Docs | Variable | Description | Default | |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | `TREK_API_DOCS_ENABLED` | Serve interactive OpenAPI/Swagger docs at `/api/docs` (raw spec at `/api/docs-json`). The spec enumerates every route including the admin surface, so it is off by default. | `false` | With the flag on, `/api/docs` lists every REST endpoint with try-it-out; authorize with a session JWT via the Bearer button (the API accepts `Authorization: Bearer ` everywhere as the cookie fallback). Request bodies validated with Zod are documented automatically from the same schemas. --- ## Booking Import (KDE Itinerary) | Variable | Description | Default | |-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------| | `KITINERARY_EXTRACTOR_PATH` | Full path to the `kitinerary-extractor` binary. When unset, TREK searches `/usr/lib/*/libexec/kf6/kitinerary-extractor` and then `PATH`. Set this if you install the binary to a non-standard location. | auto-detected | The official TREK Docker image bundles the binary automatically: on both amd64 and arm64 it installs `libkitinerary-bin` via apt (Debian trixie) and symlinks it to `/usr/local/bin/kitinerary-extractor`, which the image also pins via `KITINERARY_EXTRACTOR_PATH`. When running TREK from source, install `libkitinerary-bin` (Debian trixie / Ubuntu 25.04+). There is no static binary to download; to run a newer extractor than your distribution packages, build one and point `KITINERARY_EXTRACTOR_PATH` at it, or derive an image from the official one with your own apt sources. The extractor's version is in the startup log and in `GET /api/admin/system-info` (admin only) — worth checking before reporting that a provider is unsupported, since a missing vendor script and an unsupported provider both come back empty. `LOG_LEVEL=debug` additionally passes the extractor's raw stderr through, including the `JS ERROR` lines that name a failing script; it is read once at startup, so it needs a restart. The `GET /api/health/features` endpoint returns `{ "bookingImport": true }` when the binary is found. The Import button in the Reservations panel is hidden only when **neither** extractor is available — an instance running the AI Parsing addon still offers it with `bookingImport: false`. Booking import can also fall back to an AI model for documents KDE Itinerary can't read. That feature (the **AI Parsing** addon) is configured in the UI; the only environment variable it reads is `LLM_TIMEOUT_MS` below. See [AI-Booking-Import](AI-Booking-Import). --- ## Public Transit (Transitous) Public-transit routing in the planner is powered by [Transitous](https://transitous.org/), a free community MOTIS service — no API key is required. See [Transport: Flights, Trains, Cars](Transport-Flights-Trains-Cars) for the feature itself. An admin can switch the backend under **Admin → Settings → API Keys → Transit Provider**: **Transitous (free)** is the default and the fallback, **Google** answers with the Google API key set on the same card and is billed per search. While no Google key is set, searches keep going to Transitous. `TRANSIT_API_URL` below only applies to the Transitous path. | Variable | Description | Default | |-------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| | `TRANSIT_API_URL` | Base URL of the transit routing API. TREK's server proxies requests to it. Point this at your own self-hosted [MOTIS](https://github.com/motis-project/motis) instance if you want zero third-party egress. A trailing slash is stripped. | `https://api.transitous.org` | When left at the default, using the transit feature makes the TREK **server** send outbound HTTPS requests to `api.transitous.org` (with an identifying User-Agent, as the Transitous usage policy asks). No transit request is made until a user actually searches for a journey. --- ## Image Search (Unsplash) TREK can search [Unsplash](https://unsplash.com/) for **trip cover images** and **place images**. By default the server queries Unsplash's public web endpoint **without an API key**, so no configuration is needed on most installs. Some hosting environments — commonly VPS and datacenter IP ranges (and many Kubernetes clusters) — are **blocked or rate-limited** by that unauthenticated endpoint, which surfaces in the UI as **"Unsplash search unavailable"**. Configuring a free Unsplash Access Key switches the server to Unsplash's official, authenticated API (`api.unsplash.com`), which is not subject to that block. See [issue #1449](https://github.com/liketrek/TREK/issues/1449). | Variable | Description | Default | |-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------| | `UNSPLASH_ACCESS_KEY` | Unsplash **Access Key** used to authenticate cover/place image search against `https://api.unsplash.com`. When set, it takes priority over any key configured per-admin in **Admin → Settings**. When unset, the server falls back to the unauthenticated endpoint (which some datacenter/VPS IPs are blocked from). Get a free key at [unsplash.com/developers](https://unsplash.com/developers). | unauthenticated endpoint | **Two ways to configure it** — pick one; the env var wins if both are present: 1. **Environment variable** (this page) — instance-wide, ideal for Docker/Helm/Unraid where you already manage config as env. The **Unsplash API Key** field in the admin panel is then read-only and names the variable. 2. **Admin → Settings → API Keys** — paste the key into the **Unsplash API Key** field. Stored encrypted at rest and used as a fallback for every user when no env var is set. This is the better option if you'd rather not restart the container to change it. To get a key: create a free account at [unsplash.com/developers](https://unsplash.com/developers), register a new application, and copy its **Access Key** (not the Secret Key). The Unsplash free tier (demo) allows 50 requests/hour, which is ample for cover search. --- ## Place Search (TREK Places API) TREK's own place index, the [TREK Places API](TREK-Places-API), answers the suggestions and, together with OpenStreetMap, the full search in the place form, the category buttons on the trip map, the Road trip search along the drive, import geocoding and the offline download of a trip's surroundings. It needs no key, it is on by default, and there is no switch for it in the admin panel: whether searches leave the instance is set here. | Variable | Description | Default | |-----------------------|-------------|---------| | `TREK_PLACES_ENABLED` | Set to `false` to stop asking the index. Search then works as it did before 4.3.0: OpenStreetMap on an install without a key, the keyed provider (Google or Amap) when one is set, the Overpass mirrors for the category buttons, and no places downloaded for offline search. It is a boolean switch like the others on this page: `false`, `0`, `off` and `no` in any casing switch it off, a value that is not boolean-like aborts startup, and unset or blank leaves the index on. | on | | `TREK_PLACES_URL` | Base URL of a copy of the service you run yourself; it has to answer the same `/v1` API. Unset or blank uses the public service. A trailing slash is stripped, and a value that is not a full URL aborts startup. It is configuration rather than user input and is not run through the SSRF guard, so an address on your LAN or Docker network works without `ALLOW_INTERNAL_NETWORK`. | `https://places.liketrek.com` | On Helm both go under `env:` in `values.yaml`. The chart passes `TREK_PLACES_ENABLED` through whenever it is set at all, so an unquoted `false` or `--set env.TREK_PLACES_ENABLED=false` reaches the container as well. --- ## Place Search (Google Places) A Google Maps API key gives place search Google as the keyed provider beside the TREK Places index and OpenStreetMap, and switches on Google photos, place details and the Google transit backend. What the key is used for, and the switches that limit it, are on [Places and Search](Places-and-Search#with-a-google-maps-api-key). | Variable | Description | Default | |------------------|-------------|---------| | `PLACES_API_KEY` | Google Maps API key with the **Places API (New)** enabled. When set, it takes priority over the key configured in **Admin → Settings → API Keys** and is used for every member of the instance. | unset | **Two ways to configure it**, pick one; the env var wins if both are present: 1. **Environment variable** (this page): instance-wide, ideal for Docker/Helm/Unraid where you already manage config as env. In the chart it is a credential and goes under `secretEnv:`, not `env:`. 2. **Admin → Settings → API Keys**: paste it into the **Google Maps API Key** field. Stored encrypted at rest. While the variable is set, the **Google Maps API Key** field in the admin panel is read-only and names `PLACES_API_KEY` instead of showing a value. **Test** beside it still works and checks the key from the environment. The same goes for `UNSPLASH_ACCESS_KEY` and `AMAP_API_KEY` and their fields. The value itself never reaches the browser. If you restrict the key to **HTTP referrers** in Google Cloud Console, set `APP_URL` as well: TREK sends it as the `Referer` header on every Google request, and without it Google rejects them. --- ## Place Search (Amap / 高德地图) Google Places is unreachable from most networks inside mainland China, and OpenStreetMap's coverage of Chinese POIs (restaurants, shops, the things a trip is actually made of) is thin and rarely in Chinese. TREK can put [Amap (高德地图)](https://lbs.amap.com/) in the slot Google otherwise holds: the TREK Places index and OpenStreetMap still answer first, Amap answers when they have nothing, and autocomplete, place details and reverse geocoding go through it. There is an Amap basemap to match (see [[Map Settings|Map-Settings]]). | Variable | Description | Default | |-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------| | `AMAP_API_KEY` | Amap **Web 服务** (web service) key. When set, it takes priority over any key configured in **Admin → Settings → API Keys**. | unset | | `AMAP_API_SECRET` | The private secret (数字签名) of that key. Required exactly when the key was created with signing enabled: such a key rejects every unsigned request. Leave unset otherwise. | unset | | `AMAP_API_BASE` | Send the calls somewhere other than `https://restapi.amap.com`: an egress proxy, a cache, a gateway that holds the credential. The replacement has to speak the same API. | `https://restapi.amap.com` | **Get a key** at [console.amap.com](https://console.amap.com/dev/key/app): create an application, then add a key of type **Web 服务**. A **Web 端 (JS API)** key is a different kind of credential and will be rejected with `INVALID_USER_KEY`. This is the single most common misconfiguration. **Two ways to configure it**, pick one; the env var wins if both are present: 1. **Environment variable** (this page): instance-wide, ideal for Docker/Helm where you already manage config as env. In the chart the key and the secret are credentials and go under `secretEnv:`, not `env:`; only `AMAP_API_BASE` is a plain `env:` value. The **Amap (高德地图) API Key** field in the admin panel is then read-only and names the variable. 2. **Admin → Settings → API Keys**: paste it into the **Amap (高德地图) API Key** field. Stored encrypted at rest. Setting a key is not enough on its own: **Admin → Settings → API Keys → Place search provider** decides which keyed provider answers. `Automatic`, the default, keeps Google when a Google key is configured, then takes Amap, then nobody, so adding an Amap key never silently moves an existing install off Google. Choose **Amap** explicitly to make it the provider. ### Coordinates Amap speaks **GCJ-02**, the offset datum Chinese law requires published maps to use; everything TREK stores is **WGS-84**. The conversion happens at the boundary, in both directions, so what lands in the database, in a GPX export or on a map is always WGS-84: a place added through Amap and opened in OpenStreetMap later is in the right spot. The place also keeps the Amap id it was found by, so it keeps opening against Amap whichever provider is selected later. You do not need to configure anything for this, but it is worth knowing if you compare raw coordinates against Amap's own website, which will differ by a few hundred metres. --- ## Storage & Paths Storage backends and category assignment are configured in [[Admin: Storage|Admin-Storage]] (or a seed-once `storage-config.json`), not through environment variables. `TREK_PLACE_PHOTO_DIR` below is unaffected. | Variable | Description | Default | |------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------| | `TREK_PLACE_PHOTO_DIR` | Directory where cached Google place photos are stored. Created recursively on boot. Set this to point photo storage at a dedicated mounted volume. | `uploads/photos/google` | | `FILE_UPLOAD_LIMIT_MB` | Maximum size (in MB) of a file uploaded to a trip, a booking or a collab note, and of a file Document Sync brings in. The upload dialogs use the same limit and refuse a larger file before the upload starts. Videos keep their own 500 MB cap, and trip covers and place images their 20 MB. The MCP tool `upload_trip_file` takes at most 10 MB, or this limit when it is lower. If you sit behind a reverse proxy, raise its upload limit as well. | `50` | | `BACKUP_UPLOAD_LIMIT_MB` | Maximum **compressed** size (in MB) of a restore-backup archive that may be uploaded. Raise it if your backups (which include the `uploads/` directory) exceed the default. Non-positive or invalid values abort startup. | `500` | | `RESTORE_FROM_BACKUP` | Path to a backup ZIP to restore on the **first** start, before any setup. Only acts while no database exists; afterwards it is ignored with a warning. A backup that fails the restore checks stops the start. See [Backups](Backups#restoring-on-a-new-install-before-setup). | none | | `BACKUP_MAX_DECOMPRESSED_MB` | Maximum **decompressed** size (in MB) of a restore-backup archive — the zip-bomb guard. Independent of `BACKUP_UPLOAD_LIMIT_MB` and enforced on both restore paths, so a restore that fits the upload cap can still be refused with `Backup exceeds the maximum decompressed size.` Raise both when restoring a very large instance. | `5120` (5 GB) | | `TREK_DB_JOURNAL_MODE` | SQLite [journal mode](https://sqlite.org/pragma.html#pragma_journal_mode): `DELETE`, `TRUNCATE`, `PERSIST`, `MEMORY`, `WAL` or `OFF`. Set `DELETE` when the data directory lives on network storage — see below. Values SQLite doesn't know log a warning and fall back. | `WAL` | | `TREK_DB_PRE_MIGRATE_SNAPSHOT` | Copy the database to `pre-migrate--