Skip to main content

Configuration reference

Every environment variable the API reads, grouped the way .env.example is grouped. Required variables have no default and the API refuses to start without them.

App​

VariableDefaultNotes
NODE_ENVdevelopmentproduction inside the container image.
PORT8080
HOST0.0.0.0

Database​

VariableDefaultNotes
DATABASE_URLrequiredThe app connects as wickermoney_app — a non-superuser, non-owner role. Row-level security is unconditionally bypassed by superusers, so this is load-bearing, not a convention.
DATABASE_OWNER_URLrequiredSame database, connected as the owner. Only used for migrations (creating schemas, policies, SECURITY DEFINER functions) — never for reading application data, and never a "migrate from" source.
APP_DB_PASSWORDrequiredMigrations create wickermoney_app as NOLOGIN with no password (a migration file is the wrong place for a secret) and grant LOGIN using this value. Must match the password in DATABASE_URL.
APP_DB_ROLEwickermoney_appOverride only when several environments share one PostgreSQL server — roles are cluster-wide, so two environments reusing a role name means whichever migrated last owns the password.

Logging​

VariableDefaultNotes
LOG_LEVELinfofatal | error | warn | info | debug | trace | silent. Worth leaving at info or above: the API returns a generic "Something went wrong" for unexpected failures on purpose and keeps the real cause — including the PostgreSQL error code, detail and hint — only in the log. At silent, that's gone.

Auth​

VariableDefaultNotes
AUTH_SECRETrequiredGenerate with openssl rand -base64 48. Signs access tokens and derives the key that signs the per-request tenant context every row-level security policy checks. After changing it, re-run migrations before starting the API, or it refuses to boot.
AUTH_ACCESS_TTL_SECONDS900Access token lifetime (15 min).
AUTH_REFRESH_TTL_SECONDS2592000Refresh token lifetime (30 days). The refresh token is an HttpOnly, SameSite=Strict cookie scoped to /api/v1/auth, rotated on every use — presenting an already-used one revokes the whole session as suspected theft.
COOKIE_SECUREtrue in productionWhether the refresh cookie carries Secure (HTTPS-only). Browsers refuse a Secure cookie over plain HTTP, so local development on http://localhost needs false.
REGISTRATION_ENABLEDtrueWhether POST /api/v1/auth/register accepts new accounts. Set false once your accounts exist. Note: while open, registering an address that already has an account returns 409 email_taken — a deliberate tradeoff for a self-hosted instance, bounded by the rate limits below.
TRUST_PROXYfalseSet true only when the API runs behind a reverse proxy you control, so the rate limiter reads the real client address from X-Forwarded-For. Left true while exposed directly, clients can spoof their address and dodge every rate limit.
AUTH_RATE_LIMIT_MAX10Requests per client address per window, on each of register/login/refresh.
AUTH_RATE_LIMIT_WINDOW_SECONDS60
AUTH_EMAIL_RATE_LIMIT_MAX5Stricter cap on login/registration attempts per email address per window, regardless of source address.
AUTH_MAX_CONCURRENT_HASHES4Argon2id hashes (~46 MiB each) computed at once; excess work queues rather than spiking memory.

Rate limit counters live in memory — per API process, reset on restart.

Web app​

VariableDefaultNotes
WEB_DIST_DIRunsetDirectory holding the built web app. When set, the API serves the UI and the plugin remotes itself, on the same origin as /api — which the refresh cookie and same-origin plugin loading both rely on. The container image sets this to /app/web; leave it unset in development, where Vite serves the UI on :5173 and proxies /api to the API.
PLUGIN_REMOTE_ORIGINSunsetAdditional origins plugin remotes may load from, beyond the app's own origin. Feeds directly into the script-src/connect-src Content-Security-Policy — don't add an origin here you don't trust.

Reverse proxy notes​

Wicker Money is one process on one port by design — the API serves the built web app and the plugin remotes itself, so a reverse proxy is optional, not required. If you do put one in front (for TLS termination, a shared ingress, etc.), set TRUST_PROXY=true and forward X-Forwarded-For.