Backend

Backend configuration#

cp .env.example .env

Required#

VariableWhat it is
DATABASE_URLPostgreSQL connection string
CRYPTO_SECRETEncrypts stored user data — openssl rand -hex 32

CRYPTO_SECRET is generated once and never changed. Rotating it does not re-encrypt anything — it makes every existing account's data unreadable.

Optional#

VariableDefaultWhat it does
PORT3000Port to listen on.
META_NAMEMy P-Stream BackendShown by /meta, so a client can display whose instance it's talking to.
META_DESCRIPTIONSame, one line of prose.
DB_PROVIDERplain postgresSet to supabase to use DATABASE_URL_SUPABASE instead.
DATABASE_URL_SUPABASEOnly read when DB_PROVIDER=supabase.
CAPTCHAfalsetrue puts Cloudflare Turnstile on register and login.
CAPTCHA_CLIENT_KEYTurnstile site key. Required when CAPTCHA is on.
CAPTCHA_SECRETTurnstile secret key. With CAPTCHA on but this unset, nothing is verified.
MAX_WATCH_SERIES777How many distinct titles the watch counter keeps before dropping the least recently watched.
RESET_METRICSfalseSet to true for exactly one boot to wipe persisted counters, then remove it.

Metrics#

Counters survive restarts on their own — a snapshot is written to a metrics table in the same Postgres database, so there is nothing extra to mount.

MAX_WATCH_SERIES exists because title ids never stop arriving. The set of watched series is capped and the least recently watched fall off the end; raise it for a longer tail at the cost of a larger /metrics response.

Keep Postgres off the internet#

Docker writes its own iptables rules and goes straight through ufw, so a published 5432 is your database on the public internet regardless of what your firewall says. Use expose rather than ports, and let the backend reach it by service name.