Configuration
Everything is an environment variable. Nothing about how to reach the database is stored in the database, which resolves the obvious circularity and means a misconfigured instance fails at boot rather than halfway through a request.
Mail is the one exception, and only additively: it can also be set from the admin, and an environment variable always wins over what is stored.
Choosing a backend
There is no BACKEND= variable. Whichever backend's variables are present is the one selected, in this order:
| Variables present | Backend |
|---|---|
OPB_POSTGRES_URL, DATABASE_URL or POSTGRES_URL |
Postgres |
| Supabase URL and service key | Supabase |
Neon connection string plus BLOB_READ_WRITE_TOKEN |
Neon + Vercel Blob |
| none of the above | Local filesystem |
The filesystem backend is refused in production on hosts that discard the disk.
Required in production
| Variable | What happens without it |
|---|---|
OPB_SETUP_TOKEN |
The site cannot be claimed on a public host. Locally there is a short boot window instead. |
OPB_SECRET_KEY |
Credentials saved from the admin cannot be encrypted, so saving them is refused. |
OPB_SITE_URL |
No passphrase reset and no newsletter sign-ups: both need an absolute link and neither will build one from a request header. Canonical links fall back to the host. |
Storage
| Variable | Default | Notes |
|---|---|---|
OPB_DATA_DIR |
.opb beside the app |
Where uploads live. Set it to a mounted volume in a container. |
OPB_POSTGRES_URL |
— | Also accepted as DATABASE_URL or POSTGRES_URL. |
BLOB_READ_WRITE_TOKEN |
— | Vercel Blob. Selects blob storage for media. |
All optional. Presence of OPB_SMTP_HOST alone selects SMTP.
| Variable | Default |
|---|---|
OPB_SMTP_HOST |
— |
OPB_SMTP_PORT |
587 |
OPB_SMTP_USER / OPB_SMTP_PASSWORD |
— (Mailpit needs neither) |
OPB_SMTP_SECURE |
0. Set to 1 only for implicit TLS on port 465. |
OPB_MAIL_FROM |
no-reply@<your SMTP host> |
Credentials are never sent over a cleartext connection: when a username is set, STARTTLS is required rather than optional, so a server that does not offer encryption produces a visible failure instead of a password on the wire.
Development and demo
| Variable | Meaning |
|---|---|
OPB_ALLOW_INSECURE_COOKIES |
1 to test a production build over plain HTTP locally. Without it the session cookie is Secure, the browser drops it, and sign-in appears to do nothing at all. |
OPB_DEMO_MODE |
1 gives every visitor their own in-memory copy, discarded after an hour. Uploading, sending mail and saving credentials are switched off. |
OPB_GITHUB_CLIENT_ID |
Enables reporting bugs from inside the admin, via GitHub's device flow. No client secret is involved. |
Analytics
Cookieless, and nothing is recorded until set.
| Variable | Meaning |
|---|---|
NEXT_PUBLIC_ANALYTICS_PROVIDER |
plausible or umami |
NEXT_PUBLIC_ANALYTICS_DOMAIN |
The site being measured |
NEXT_PUBLIC_ANALYTICS_WEBSITE_ID |
Umami only |
NEXT_PUBLIC_ANALYTICS_SRC |
A self-hosted script URL |
A note on secrets
A build-time check greps the client bundle for the value of every secret and fails if one appears. This exists because the project's own first version compiled its admin passcode into the public JavaScript.