A single .env at the repo root drives every service. Do not create per-service env files — docker-compose.yml interpolates everything from the root file, and the per-service .dockerignore prevents accidental copies leaking into images.
| Var |
Purpose |
NEXTAUTH_URL |
Public URL of the web service. http://localhost:3000 in dev; your domain in prod. |
NEXTAUTH_SECRET |
NextAuth session secret. Generate with openssl rand -base64 32. |
GITHUB_CLIENT_ID |
GitHub OAuth app client ID. |
GITHUB_CLIENT_SECRET |
GitHub OAuth app client secret. |
ALLOWED_GITHUB_USER |
The single GitHub login allowed to sign in. Everyone else is rejected at the OAuth callback. |
INTERNAL_API_SECRET |
Shared HMAC key for the internal web↔api JWT. Use the same value on both services. |
VOYAGE_API_KEY |
Voyage AI key for embeddings. |
OPENROUTER_API_KEY |
OpenRouter key for the LLM. |
ADMIN_REINDEX_TOKEN |
Token gating POST /admin/reindex. The vault syncer reuses it. |
| Var |
Purpose |
DOMAIN |
Public hostname Caddy serves (e.g. brain.example.com). |
NEXT_PUBLIC_DOMAIN |
Same value as DOMAIN, exposed to the browser for the serverActions.allowedOrigins check. |
ACME_EMAIL |
Email Let's Encrypt uses for cert expiry / incident notifications. |
VAULT_REPO |
Git URL the vault-syncer clones. Public or private. |
VAULT_BRANCH |
Branch to track (default main). |
VAULT_GIT_TOKEN |
Required only if VAULT_REPO is private. Use a fine-grained PAT scoped to that one repo with Contents: read-only. |
| Var |
Default |
Purpose |
API_BASE_URL |
http://api:8000 |
Internal hostname web uses to reach api. |
VAULT_PATH |
/vault |
Path inside the api container that contains wiki/ and raw/. Set to /vault/<inner-dir> if your vault repo has an inner directory. |
CHROMA_PATH |
/data/chroma |
ChromaDB persistent location inside the container. |
OPENROUTER_BASE_URL |
https://openrouter.ai/api/v1 |
Override only to point at a proxy. |
LLM_MODEL |
anthropic/claude-sonnet-4 |
Answer model. Any OpenRouter ID. |
ALLOWED_ORIGINS |
http://web:3000 |
FastAPI CORS allowlist (comma-separated). |
RATE_LIMIT_PER_MINUTE |
60 |
slowapi per-IP rate limit. |
SYNC_INTERVAL_MIN |
5 |
vault-syncer poll interval. |
CF_API_TOKEN |
(empty) |
Cloudflare API token. Required only if you switch Caddy to the DNS-01 ACME challenge. Permissions: Zone:DNS:Edit + Zone:Zone:Read, scoped to your zone only. |
TS_AUTHKEY |
(empty) |
Tailscale reusable auth key. Required only when running with the docker-compose.tailscale.yml overlay. |
TS_HOSTNAME |
brain |
Hostname the Tailscale sidecar registers under. |
- Generate secrets with
openssl rand -base64 32. scripts/new-secret.sh is a thin wrapper.
- Never commit
.env. It's in .gitignore; only .env.example is tracked.
- Rotate the GitHub PAT that backs
VAULT_GIT_TOKEN on a schedule. Set an expiry on the token.
INTERNAL_API_SECRET is shared between two services. Rotating it requires a coordinated restart of both web and api.
ADMIN_REINDEX_TOKEN is held by the syncer container and used by scripts/seed-embed.sh. It is not exposed to the browser.