# Configuration Grimoire reads its configuration from environment variables. Most settings people change day to day (registration, SMTP, retrieval thresholds, backups, forge connections) live in **Settings** in the web UI instead. Any variable marked *secret* can also be read from a file: set `_FILE` to the path, for example a Docker secret at `/run/secrets/grimoire_key`. ## Environment variables ### Core | Variable | Default | Description | |----------|---------|-------------| | `DATABASE_URL` *(secret)* | `postgresql+asyncpg://grimoire:grimoire@localhost:5432/grimoire` | PostgreSQL connection string, using the `asyncpg` driver | | `SECRET_KEY` *(secret)* | kept in `DATA_DIR` | Signs sessions and seals stored credentials. Unset, the instance makes its own on first start and keeps it in `DATA_DIR/secret_key`. Set it only to manage the key yourself; it wins over the kept one (see below) | | `DATA_DIR` | `/data` | Where the instance keeps its own `SECRET_KEY`. Mount a volume here | | `BASE_URL` | `http://localhost:5000` | Public URL, used in email links and OIDC redirects | | `TRUSTED_PROXY_HOPS` | `1` | The starting count of reverse proxies in front of Grimoire. **Settings → Config → Reverse proxy** is where the count is checked and set, and a count saved there wins over this. The client address and the scheme are read that many entries in from the right of `X-Forwarded-For` and `X-Forwarded-Proto`; `0` ignores both headers. See [Behind a reverse proxy](#behind-a-reverse-proxy) | | `LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING` or `ERROR` | | `LOG_RETENTION_DAYS` | `90` | Days of app logs to keep | | `FASTEMBED_CACHE_DIR` | `/data/fastembed-cache` | Where the embedding model is stored after its first download | ### Sign-in | Variable | Default | Description | |----------|---------|-------------| | `LOCAL_AUTH_ENABLED` | `true` | `false` turns off username/password login, leaving SSO only | | `OIDC_ISSUER` | — | OIDC issuer URL | | `OIDC_CLIENT_ID` | — | OIDC client ID | | `OIDC_CLIENT_SECRET` *(secret)* | — | OIDC client secret | | `OIDC_SCOPES` | `openid profile email` | Scopes to request | SSO turns on when the issuer, client ID and secret are all set. See [SSO / OAuth](sso-oauth.md). ### Email Admins can set SMTP in **Settings**; these are the defaults until they do. | Variable | Default | |----------|---------| | `SMTP_HOST` | — | | `SMTP_PORT` | `587` | | `SMTP_USERNAME` | — | | `SMTP_PASSWORD` *(secret)* | — | | `SMTP_FROM_ADDRESS` | — | | `SMTP_FROM_NAME` | `Fabled Grimoire` | | `SMTP_USE_TLS` | `true` | ### Integrations | Variable | Default | Description | |----------|---------|-------------| | `PLUGIN_MARKETPLACE_URL` | this repo's git URL | The git URL shown in the plugin install command under Settings → MCP Access. An admin can override it in Settings | | `FORGE_KIND`, `FORGE_BASE_URL`, `FORGE_TOKEN` *(secret)* | — | A git forge connection for admins' projects, so a token can live in a Docker secret. Each user can add their own under Settings → Integrations; a stored connection for the same host wins | | `FORGE_WEBHOOK_SECRET` *(secret)* | — | Verifies push webhooks from the forge | | `SEARXNG_URL` | — | A SearXNG instance, listed as the web-search backend under Settings → Integrations | ## Running it ### Docker Compose [`docker-compose.yml`](../docker-compose.yml) runs the published image with its own database, and needs nothing set: ```bash docker compose up -d ``` Open `http://localhost:5000`. The first account registered becomes the admin; after that, registration stays closed until an admin opens it in Settings or sends an invite. Update with `docker compose pull && docker compose up -d`. The key and the data stay in their volumes. ### From source ```bash docker compose -f docker-compose.dev.yml up --build ``` ### Docker Swarm [`docker-compose.prod.yml`](../docker-compose.prod.yml) is a stack file: the app and database on a private overlay network, trusting one proxy (`TRUSTED_PROXY_HOPS: "1"`). A Swarm can move the app to another node, away from a node-local `/data` volume, so the stack sets its own key: provide `DB_PASSWORD` and `SECRET_KEY` in the environment (or mount a Docker secret and set `SECRET_KEY_FILE`): ```bash DB_PASSWORD=... SECRET_KEY=... docker stack deploy -c docker-compose.prod.yml grimoire ``` The database user, database and volume default to `grimoire`. An install created under the name Scribe keeps its data by setting `POSTGRES_USER`, `POSTGRES_DB` and `PGDATA_VOLUME` to the names it was created with. The shipped files leave out healthchecks and restart policies on purpose. The app waits for its database on startup, and the orchestrator's defaults restart it if it ever exits. ### Volumes - **Database** (`pgdata`): everything Grimoire stores. - **App data** (`/data`): the instance's own `SECRET_KEY`, the embedding model cache and diagnostics. Losing it costs the key: Settings then asks for saved forge tokens and the backup secret again, and the model downloads again. Back up the database with **Settings → Data → Scheduled backups** ([Backups](backups.md)). A database backup doesn't carry the key; to move sealed credentials to a new instance, copy `/data/secret_key` with it. ## Behind a reverse proxy Grimoire doesn't terminate TLS. Put it behind Nginx, Traefik or Caddy, forward to port 5000, and keep port 5000 off the internet. Set `BASE_URL`, then set the proxy count to the number of proxies a request passes through: one proxy is `1`, the default; Cloudflare's proxied DNS in front of your own proxy is `2`. **Settings → Config → Reverse proxy** sets the count and checks it against your own request. It shows the address Grimoire reads you as, and what each count from 0 to 3 would read. Pick the count that shows your own address. When a request comes through Cloudflare, the section names the count that reads the address Cloudflare reports, with a button that sets it. `TRUSTED_PROXY_HOPS` in the environment is only the starting value: a count saved in Settings wins, and **Use the environment's** goes back to it. A count that is too low makes every visitor read as the proxy, so they share one sign-in rate limit and a few bad sign-ins block everyone. The "too many attempts" message says so and points here. The block on sign-ins lifts after a minute; an admin who is already signed in can fix the count at once. ```nginx location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # MCP responses can stream. proxy_buffering off; proxy_read_timeout 600s; } ``` Each proxy appends its entry to `X-Forwarded-For` and `X-Forwarded-Proto`, so Grimoire reads the entry that many in from the right: the one your own proxy wrote. Whatever a client puts to the left of it is ignored. A header with fewer entries than that (a LAN road with one proxy beside a Cloudflare road with two) is read at its leftmost entry, the one the first proxy wrote. When that entry says `https`, the session cookie is marked `Secure` and the response carries `Strict-Transport-Security: max-age=31536000`. Over plain HTTP neither is sent, and Grimoire never redirects to HTTPS, so a LAN install keeps working. A hop count higher than the real number of proxies lets a client choose its own address, so count them. With `0`, the headers are ignored and the socket peer is the client: use it when clients reach port 5000 directly. ## Security checklist - **Keep the key.** Forge tokens and the backup destination's S3 secret are sealed under a key derived from `SECRET_KEY`, so a copy of the database alone can't read them. Unset, the instance makes a random key and keeps it in `/data/secret_key`, readable by its owner only; keep that volume. If the key changes (say, a restore onto an instance with a different key), Settings marks those credentials and asks for them again; nothing else breaks. The app warns at startup when `SECRET_KEY` is a public placeholder or `/data` is not a mounted volume, and refuses to start with a placeholder key (`dev-secret-change-me`, `change-me-in-production`). - **The proxy count** (Settings → Config → Reverse proxy) matches the number of proxies in front of Grimoire, so rate limits and the audit log see the real client and the cookie is `Secure` over HTTPS. The section shows whether it does. - **Registration** is open only for the first account. Invite people, or open it briefly from Settings. - **Signing everyone else out**: changing a password ends that user's other sessions; Settings → Account has a button for it too. - **API keys** are per user, shown once, and can be read-only. Revoke any you no longer use under Settings → MCP Access.