Stoop is in beta. These docs are still being written, so expect gaps and changes.
Configuration reference
Set any of these in .env; the compose file passes that file through to
the server. Two are pinned by the compose file itself and ignore what
.env says: STOOP_STORAGE_DIR and STOOP_LIVEKIT_KEY_FILE.
STOOP_DATABASE_URL defaults to the bundled Postgres; set it only when
using your own.
| Variable | Default | Purpose |
|---|---|---|
STOOP_DATABASE_URL |
(required) | Postgres connection string |
STOOP_DATABASE_POOL_MAX |
0 |
Most connections Stoop opens to Postgres. 0 is the larger of 4 and the CPU count; otherwise at least 2. Keep it under Postgres max_connections, less what backups and psql need. Wins over pool_max_conns in the URL |
STOOP_LISTEN_ADDR |
:8080 |
HTTP bind address |
STOOP_PUBLIC_URL |
(empty) | The address people use to reach the server; invite links use it, its host is an allowed WS origin. Defaults to the tailnet address with the built-in Tailscale listener |
STOOP_TRUST_PROXY |
false |
Believe X-Forwarded-For / X-Forwarded-Proto from every caller, taking the header’s rightmost address as the client. Blunt, and spoofable unless the proxy sets or appends the header itself; prefer STOOP_TRUSTED_PROXIES. Can’t be combined with it |
STOOP_TRUSTED_PROXIES |
(empty) | Comma-separated addresses or CIDR ranges of your reverse proxy or tunnel (172.18.0.0/16, 192.168.1.5); only those callers’ X-Forwarded-* headers are believed. A list saved under Server admin → Hosting → Trusted proxies overrides it |
STOOP_SECURE_COOKIES |
false |
Force session cookies Secure on every listener. Rarely needed: TLS listeners and trusted HTTPS proxies get it automatically |
STOOP_ALLOWED_WS_ORIGINS |
localhost:*,127.0.0.1:* |
Extra WebSocket origin patterns. The request’s own host (and STOOP_PUBLIC_URL’s) is always allowed, so this is only needed behind a proxy that rewrites Host |
STOOP_AUTH_RATE_LIMIT |
20 |
Sign-in and registration attempts allowed per client address per minute. 0 disables (dev/e2e only). The per-account lockout after 5 wrong passwords is always on |
STOOP_SIGNALING_RATE_LIMIT |
30 |
New voice signaling connections per client address per minute (the LiveKit proxy is unauthenticated; this keeps it from being an open relay). 0 disables |
STOOP_SEARCH_RATE_LIMIT |
30 |
Message searches per user per minute. 0 disables |
STOOP_REGISTRATION |
invite |
Seeds the registration policy on first boot only (open, invite, closed); change it later from the admin page |
STOOP_INSTANCE_NAME |
(random, e.g. Chalk Avenue) |
The server’s name, shown in the browser tab. Unset, a random two-word name is picked on first boot and kept, so several instances never all call themselves “Stoop”. The admin page’s saved value overrides it |
STOOP_STORAGE |
fs |
File storage backend. fs is the only one; any other value (including s3) refuses to start |
STOOP_STORAGE_DIR |
./data |
Directory for uploaded files (compose: /data on the stoop-data volume) |
STOOP_LIVEKIT_KEY_FILE |
<STOOP_STORAGE_DIR>/livekit/keys.yaml |
Where to write the LiveKit key pair for a sidecar started with --key-file. Written on every boot (minted or from the environment); the file is 0600 in a 0700 directory because LiveKit refuses a key file others can read |
STOOP_LINK_PREVIEWS |
true |
Fetch Open Graph cards for links in messages. The server fetches (readers’ browsers never contact the site); set false if the server should make no outbound requests on members’ behalf |
STOOP_UPDATE_CHECK |
true |
Read the release list on getstoop.org so Server admin can say a newer release exists, or that this one is no longer supported. Nothing about the server is sent; set false to stop the request |
STOOP_FILE_SWEEP_INTERVAL |
6h |
How often unreferenced uploads, stray blobs and old read activity items are removed; 0 turns the timer off (the admin page can still sweep) |
STOOP_FILE_SWEEP_GRACE |
24h |
How old an unreferenced file must be before the sweep takes it |
STOOP_ACTIVITY_RETENTION |
720h |
Read mention/reply/DM activity items older than this are removed on the sweep timer; 0 keeps them forever |
STOOP_UNFURL_ALLOW_PRIVATE |
false |
Let link previews fetch private/loopback addresses. Dev and tests only — it is what stops the server being used as a proxy into your LAN |
STOOP_WEBHOOKS |
true |
false stops every incoming webhook post and outgoing delivery, whatever the admin settings say; nothing is deleted |
STOOP_WEBHOOK_RATE_LIMIT |
60 |
Posts per minute one incoming webhook may make; 0 removes the limit |
STOOP_WEBHOOK_DELIVERY_RETENTION |
168h |
How long finished outgoing webhook deliveries are kept in the log; 0 keeps them forever |
STOOP_DEV_WEB_URL |
(empty) | Serve the web app from a Vite dev server at this address instead of the embedded build, allowing inline scripts for its hot reload. Development only — make dev sets it |
STOOP_LIVEKIT_URL |
(empty) | LiveKit sidecar address the app proxies signaling to, e.g. http://livekit:7880 (voice) |
STOOP_LIVEKIT_API_KEY |
(empty) | Only to reuse an existing LiveKit key pair; empty, the server mints one |
STOOP_LIVEKIT_API_SECRET |
(empty) | The secret of that pair |
STOOP_TURN_URLS |
(empty) | Comma-separated TURN URLs of your own relay (voice); needs the two below |
STOOP_TURN_USERNAME |
(empty) | Credentials for STOOP_TURN_URLS |
STOOP_TURN_CREDENTIAL |
(empty) | |
STOOP_STUN_URLS |
(empty) | STUN URLs offered alongside your relay |
STOOP_CLOUDFLARE_TUNNEL |
false |
Run cloudflared as a child process; needs the token. Also on Server admin → Hosting |
STOOP_CLOUDFLARE_TUNNEL_TOKEN |
(empty) | The remotely managed tunnel’s token |
STOOP_CLOUDFLARED_PATH |
(empty) | Where cloudflared is; empty looks on PATH. The Docker image includes it |
STOOP_CLOUDFLARE_TURN_KEY_ID |
(empty) | Cloudflare TURN key id; Stoop mints credentials per join (voice through HTTP-only tunnels / CGNAT) |
STOOP_CLOUDFLARE_TURN_API_TOKEN |
(empty) | Its API token; set together with the key id |
STOOP_TAILSCALE |
false |
Join a tailnet from inside the binary and serve HTTPS on the tailnet address (see Tailscale, built in). Settings saved on the admin page override these |
STOOP_TAILSCALE_HOSTNAME |
stoop |
Node name on the tailnet |
STOOP_TAILSCALE_AUTHKEY |
(empty) | Pre-authorise the node; otherwise a login URL is logged on first start |
STOOP_TAILSCALE_CONTROL_URL |
(empty) | Self-hosted control server (Headscale) |
STOOP_TAILSCALE_FUNNEL |
false |
Also expose the tailnet address publicly via Funnel (HTTP only — voice needs TURN) |
STOOP_TAILSCALE_VOICE |
true |
The built-in node also carries LiveKit’s media ports, so voice rides the tailnet. false serves HTTPS over the tailnet only |
STOOP_LIVEKIT_MEDIA_HOST |
127.0.0.1 (livekit in compose) |
Where the built-in node forwards media: LiveKit’s host on this machine or network |
STOOP_LIVEKIT_TCP_PORT |
7881 |
LiveKit’s TCP media port. Under compose this one setting also configures and publishes it; with a bare binary, match it to livekit.yaml |
STOOP_LIVEKIT_UDP_PORTS |
50000-50100 |
LiveKit’s UDP media range, as start-end. Same as above |
STOOP_LIVEKIT_NODE_IP_FILE |
(empty) | File Stoop writes the tailnet address to for the LiveKit sidecar’s NODE_IP. Defaults to node-ip beside STOOP_LIVEKIT_KEY_FILE, which is what lands it on the shared volume under compose |
STOOP_OIDC_ISSUER |
(empty) | One OIDC login provider from the environment: the issuer URL exactly as its discovery document states it. The admin page’s saved list overrides this |
STOOP_OIDC_CLIENT_ID |
(empty) | The provider’s client id; set together with the secret and issuer |
STOOP_OIDC_CLIENT_SECRET |
(empty) | The provider’s client secret |
STOOP_OIDC_NAME |
Continue with single sign-on |
The sign-in button’s entire text |
STOOP_OIDC_ID |
sso |
The provider’s stable id; part of the callback URL, and identities link under it |
STOOP_SESSION_LIFETIME_DAYS |
30 |
How long a sign-in lasts, 1-365 days. The admin page’s saved value overrides it; a change applies to sign-ins from then on |
STOOP_PASSWORD_SIGN_IN |
everyone |
Who may use the username/password form: everyone, admins, or off (sign in through login providers instead). The admin page’s saved value overrides it; admins are always honoured as a fallback |
Compose settings
Section titled “Compose settings”These are read by the compose file, not by the server, so they apply to the Docker Compose install only.
| Variable | Default | Purpose |
|---|---|---|
COMPOSE_PROFILES |
bundled-postgres |
Which bundled services run. Empty to use your own Postgres |
STOOP_PORT |
8080 |
The port the web app is published on |
TZ |
UTC |
Time zone of the timestamps in docker compose logs |
STOOP_DATA_PATH |
stoop-data volume |
Where uploads and the Tailscale node identity live; see Where the data lives |
POSTGRES_DATA_PATH |
postgres-data volume |
Where the bundled Postgres keeps its data |
PUID, PGID |
65532 |
The user and group Stoop runs as; match the owner of STOOP_DATA_PATH |
POSTGRES_PASSWORD |
(none) | Password of the bundled Postgres |
POSTGRES_ARGS |
(empty) | postgres -c name=value flags for the bundled Postgres; see Tuning the bundled Postgres |
NODE_IP |
(empty) | The address LiveKit offers browsers for media; see Voice |