With no SECRET_KEY set, Grimoire fell back to a fixed placeholder. The quickstart compose substituted change-me-in-production, so the README's own update step (pull, up -d) recreated the container on that placeholder, and every credential sealed under the key the user started with stopped opening. - config: with neither SECRET_KEY nor SECRET_KEY_FILE, the first start writes a random key to DATA_DIR/secret_key (mode 0600, published with a hard link so concurrent starts agree), and later starts read it back. An explicit key wins and leaves the file alone. A missing SECRET_KEY_FILE or an empty key file stops the start instead of switching keys. With no data directory (a run outside the container) the process gets a temporary key and says so. - app: warns on a public placeholder key, on a temporary key, and when /data is not a mounted volume. - Dockerfile creates /data. - docker-compose.yml is now the deploy compose (published image, both volumes, nothing to set). The build-from-source compose is docker-compose.dev.yml, and docker-compose.quickstart.yml is gone. The Swarm stack file gains the /data volume and keeps its explicit key, because a Swarm can move the app away from a node-local volume. - README: a Deploy section with the compose inline, SECRET_KEY optional, "keep both volumes", and updates that keep the key. A test holds the README's copy equal to the file. - Installs that ran on the change-me-in-production placeholder get a key of their own on the new compose; Settings then asks once for saved forge tokens and the backup secret. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.7 KiB
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 <NAME>_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 |
SECURE_COOKIES |
false |
true when Grimoire is served over HTTPS |
TRUST_PROXY_HEADERS |
false |
true behind a reverse proxy, to read the client IP from X-Forwarded-For / X-Real-IP |
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.
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 runs the published image with its own database, and needs nothing set:
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
docker compose -f docker-compose.dev.yml up --build
Docker Swarm
docker-compose.prod.yml is a stack file: the app and database on a private overlay network, with SECURE_COOKIES and TRUST_PROXY_HEADERS on. 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):
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 ownSECRET_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). 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 SECURE_COOKIES=true, TRUST_PROXY_HEADERS=true and BASE_URL.
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
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;
}
With TRUST_PROXY_HEADERS=true, make sure the proxy replaces any X-Forwarded-For the client sends.
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 whenSECRET_KEYis a public placeholder or/datais not a mounted volume, and refuses to start withdev-secret-change-mewhenSECURE_COOKIES=true. SECURE_COOKIES=truewhenever Grimoire is served over HTTPS.- 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.