Environment variables
Every variable in website’s root .env.example — the file a Compose deployment actually
reads, which is not the same file local development copies.
This list is checked against that file on every build, in both directions: a variable that disappears upstream fails, and a variable added upstream that is missing here fails too.
Every variable
Section titled “Every variable”| Variable | What it is for |
|---|---|
IMAGE_TAG | Which published image tag to run |
NODE_ENV | production or development — several refusals are production-only |
PORT | The port the app listens on |
INTERNAL_PORT | The internal-only listener, for the bot channel |
UPLOAD_DIR | Where uploads are written |
LOG_LEVEL | Console log level |
FILE_LOG_LEVEL | File log level, set separately |
LOG_TO_FILE | Whether to write a log file at all |
LOG_DIR | Directory for the log file |
LOG_FILE | Log file name |
BRAND_NAME | Site name — branding is data, not a build |
BRAND_SHORT_NAME | Short form, for tight spaces |
BRAND_TAGLINE | One line under the name |
BRAND_DESCRIPTION | Meta description |
BRAND_CONTACT_EMAIL | Published contact address |
BRAND_URL | Canonical public URL |
BRAND_ACCENT_COLOR | Accent colour |
BRAND_LOGO | Logo path |
BRAND_HERO | Hero image path |
BRAND_FAVICON | Favicon path |
DB_HOST | Database host |
DB_PORT | Database port |
DB_NAME | Database name |
DB_USER | Database user |
DB_PASSWORD | Database password |
DB_ROOT_PASSWORD | The database container's root password |
JWT_SECRET | Signs session tokens. Rotating it logs everyone out |
SECRET_ENC_KEY | Encrypts secrets at rest. Required in production, and rotating it ORPHANS every stored secret |
JWT_EXPIRES_IN | Session lifetime |
COOKIE_SECURE | auto decides Secure per request, so HTTPS and LAN HTTP both work |
COOKIE_NAME | Session cookie name. Changing it invalidates existing sessions |
TRUST_PROXY | Needed behind a reverse proxy for secure cookies, real IPs and rate limiting |
DEBUG_TRUST_PROXY | Diagnostic for the above |
TOTP_CHALLENGE_TTL | How long a pending 2FA challenge is valid |
ADMIN_USERNAME | First admin, created only when no users exist |
ADMIN_PASSWORD | First admin password. Set it before the first boot, not after |
CLIENT_ORIGIN | Dev only — the Vite origin allowed through CORS |
BOT_INTERNAL_URL | Where the Discord bot listens |
BOT_INTERNAL_KEY | Authenticates the site↔bot channel. Required in production EVEN IF you run no bot |
NTFY_BASE_URL | Push notification relay base URL |
The three worth reading twice
Section titled “The three worth reading twice”SECRET_ENC_KEY encrypts secrets at rest — OAuth client secrets, the Discord bot token,
the shard’s auth token. Changing it does not re-encrypt anything: what was stored under
the old key can no longer be read, and every stored secret has to be entered again.
COOKIE_SECURE=auto decides Secure per request, which is what lets one deployment
work both over HTTPS through a proxy and over plain HTTP on a LAN address. Forcing it either
way breaks one of those.
TRUST_PROXY is required behind a reverse proxy for secure cookies, real client IPs and
rate limiting to work at all. Without it, every request appears to come from the proxy — so
rate limiting and IP bans apply to your whole user base at once.
Where to set them
Section titled “Where to set them”A first install is Install the site, which prints
a complete .env alongside its Compose file. Afterwards,
Configuration covers what is env-configured and what
is not.
Most settings are not here. Branding, navigation, theming and the shard connection are admin-managed and live in the database, deliberately — so changing them does not mean redeploying a container.
Canonical source
Section titled “Canonical source”website’s
.env.example
is the authority, and
BACKEND_DESIGN.md
§8 covers deployment.