Configuration
Every environment variable, and which install path uses it.
Configuration lives in environment variables. Docker installs use a single
root .env; local development uses per-app .env files.
Docker installs — root .env
Written by the installer; the annotated template is .env.example.
| Variable | Required | Description |
|---|---|---|
POSTGRES_PASSWORD | yes | Database password (generated by the installer) |
BETTER_AUTH_SECRET | yes | Auth signing secret, min 32 chars — placeholders are rejected |
BETTER_AUTH_URL / CORS_ORIGIN | production | Public API URL / web origin (derived from domains in the overlay) |
VITE_SERVER_URL / VITE_COLLAB_URL | production | URLs baked into the web bundle at build time |
WEB_DOMAIN, API_DOMAIN, COLLAB_DOMAIN, ACME_EMAIL | production | Domains + Let's Encrypt email for the Caddy overlay |
# Generate each secret with:
openssl rand -base64 48VITE_* values are compiled into the web bundle at build time. Changing one requires rebuilding
the web image: docker compose ... up -d --build web.
Local development — per-app .env
For pnpm dev, each app reads its own file.
apps/server/.env
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | yes | PostgreSQL connection string |
BETTER_AUTH_SECRET | yes | Auth signing secret (min 32 chars, no placeholders) |
BETTER_AUTH_URL | yes | Public URL of the server |
CORS_ORIGIN | yes | Allowed origin of the web app |
NODE_ENV | no | development (default) / production / test |
APP_NAME | no | Display name used by auth flows (white-labeling) |
RATE_LIMIT_MAX / RATE_LIMIT_AUTH_MAX | no | Per-IP requests/minute for the API / auth routes |
RATE_LIMIT_SCIM_MAX | no | Per-IP requests/minute for SCIM (default 1200) |
INTERNAL_RUN_TOKEN | no | Bearer token for the /internal runner endpoints |
INTERNAL_RUN_TOKEN replaces the digest-specific DIGEST_RUN_TOKEN, which still works as an
alias — existing deployments need no change. One token opens every POST /internal/…/run
endpoint; unset leaves them all disabled.
Instance administration
The admin console at /admin is gated on an instance role stored in
user.role. Nothing sets that role at registration, so on a fresh install
nobody can open it — INITIAL_ADMIN_EMAIL is what breaks the chicken-and-egg.
| Variable | Required | Description |
|---|---|---|
INITIAL_ADMIN_EMAIL | no* | Promoted to instance admin at start and at registration |
IMPERSONATION_ENABLED | no | Support impersonation (default true); false refuses it entirely |
IMPERSONATION_MAX_MINUTES | no | How long an impersonated session survives on its own (default 30) |
APP_VERSION | no | Reported in the instance overview (default dev; compose sets it) |
COLLAB_INTERNAL_URL | no | Where the server reaches collab for its reachability probe |
* Not required by the schema, but without it the console is unreachable on a fresh install. Further admins are appointed inside it; the last remaining one cannot be demoted, banned or deleted.
Instance admin is not org admin. It operates the deployment and is not a member of any
organization; an org owner has no rights in the console. The console shows metadata only — reading
wiki content requires impersonation, which is audited on both ends and mirrored into the affected
person's own activity feed. Set IMPERSONATION_ENABLED=false where that capability has to be
provably absent rather than merely logged.
Outbound webhooks
See Webhooks for the payload and signature contract.
| Variable | Required | Description |
|---|---|---|
WEBHOOK_SCHEDULER_ENABLED | no | In-process runner (default true); off for serverless/cron setups |
WEBHOOK_TICK_SECONDS | no | How often the runner looks for queued deliveries (default 30) |
WEBHOOK_TIMEOUT_SECONDS | no | Per-request timeout (default 10) |
WEBHOOK_MAX_ATTEMPTS | no | Attempts before a delivery is marked failed (default 6) |
WEBHOOK_ALLOW_PRIVATE_HOSTS | no | Allow endpoints on the internal network (default false) |
Data retention
The windows are not environment variables. They are per-organization settings under Einstellungen → Daten & Fristen, so a deletion policy is always a recorded decision by a named administrator — see Retention, trash & deletion blocks. These only tune the runner that enforces them.
| Variable | Required | Description |
|---|---|---|
RETENTION_SCHEDULER_ENABLED | no | In-process retention ticker (default true) |
RETENTION_TICK_SECONDS | no | Retention sweep interval (default 3600, minimum 60) |
RETENTION_BATCH_LIMIT | no | Rows removed per category per run (default 1000) |
apps/collab/.env
| Variable | Required | Description |
|---|---|---|
DATABASE_URL | yes | Same database as the server |
BETTER_AUTH_SECRET | yes | Same secret as the server (verifies collab tokens) |
COLLAB_PORT | no | WebSocket port (default 1234) |
apps/web/.env
| Variable | Required | Description |
|---|---|---|
VITE_SERVER_URL | yes | URL the browser uses to reach the API |
VITE_COLLAB_URL | yes | WebSocket URL of the collab service (ws(s)://) |
The collab service must share BETTER_AUTH_SECRET with the server — it verifies page-scoped
collab tokens signed with it. See Collaboration architecture.
Ports at a glance
| Service | Port |
|---|---|
| web | 3001 |
| server | 3000 |
| collab | 1234 |
| postgres | 127.0.0.1:5432 |
In production, only Caddy (80/443) is published; the app ports are internal.