Skip to content

Environment variables

Marrow has two .env files: one for the FastAPI backend (api/.env) and one for the Next.js frontend (web/.env.local). When deploying with docker-compose.prod.yml, both are sourced from a single root .env (see .env.prod.example).

VariableDefaultDescription
DATABASE_URLPostgreSQL connection string. Example: postgresql://marrow:marrow@localhost:5433/marrow. Not required if POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_HOST are set instead — the API assembles the DSN from those (URL-encoding the password safely). The prod compose file uses this POSTGRES_* fallback, so it never sets DATABASE_URL.
SECRET_KEYSigning key for the session JWT. Use a long random string in production (e.g. openssl rand -hex 32).
An auth methodThe API refuses to start unless one of OIDC_ISSUER, API_KEY, or MARROW_ALLOW_ANONYMOUS=true is set. See Authentication below. Omitting all three crash-loops the container on boot.
VariableDefaultDescription
STORAGE_BACKENDlocallocal (filesystem) or r2 (Cloudflare R2).
STORAGE_PATH./storageDirectory where attachments are stored (local backend). Relative paths resolve from api/. Inside the API container, this is /data/storage and is backed by a Docker volume.
R2_ENDPOINT_URLunsetR2 S3 endpoint, e.g. https://<account-id>.r2.cloudflarestorage.com (preferred). Required when STORAGE_BACKEND=r2 unless R2_ACCOUNT_ID is set.
R2_ACCOUNT_IDunsetAlternative to R2_ENDPOINT_URL — the endpoint is derived from it.
R2_ACCESS_KEY_IDunsetR2 access key (R2 backend).
R2_SECRET_ACCESS_KEYunsetR2 secret key (R2 backend).
R2_BUCKETunsetR2 bucket name (R2 backend).

Marrow is fail-closed: it refuses to start unless at least one of OIDC_ISSUER, API_KEY, or MARROW_ALLOW_ANONYMOUS=true is configured. An unconfigured API exits on boot rather than silently serving every request. At request time, auth is resolved in priority order: OIDC session cookie → X-API-Key header → anonymous (only when MARROW_ALLOW_ANONYMOUS=true).

OIDC_ISSUER is the recommended production path — it’s the only one that gives per-user identity and RBAC. See the OIDC (optional) group below.

VariableDefaultDescription
API_KEYunsetStatic credential for the X-API-Key header, used by the CLI and scripts (marrow export/restore) and by direct HTTP callers. Bypasses all RBAC (superuser equivalent) — it is not a user login. The web app still routes through /login.
MARROW_ALLOW_ANONYMOUSunset (off)Set to true to allow unauthenticated requests. Bypasses all access control — every caller gets superuser access with no identity. Intended only for a localhost-bound dev instance; keep it off in production. When on, the no-identity app lands on /workspaces.
CORS_ORIGINShttp://localhost:3000Comma-separated list of allowed origins.

Set OIDC_ISSUER to enable. All other OIDC vars are required when enabled.

VariableDescription
OIDC_ISSUEROIDC discovery URL, e.g. https://accounts.google.com.
OIDC_CLIENT_IDClient ID from your IdP.
OIDC_CLIENT_SECRETClient secret from your IdP.
OIDC_REDIRECT_URIWhere the IdP redirects after login. Must match what’s registered. Example: http://localhost:8000/api/auth/callback.
FRONTEND_URLBase URL of the web app. Used as the post-login redirect target.
COOKIE_DOMAINDomain for the marrow_session cookie. For dev: localhost. For prod with split subdomains: .marrow.so.

See OIDC for setup walkthroughs.

These apply only to the hosted Cloud deployment (SAAS_MODE=true). Self-hosted instances leave them unset — billing is never gated off-SaaS.

VariableDefaultDescription
SAAS_MODEunset (off)true enforces the subscription gate. Off for self-hosted.
STRIPE_SECRET_KEYunsetStripe API secret key.
STRIPE_WEBHOOK_SECRETunsetSigning secret for verifying Stripe webhook payloads.
STRIPE_*_PRICE_*unsetPer-tier price IDs — `STRIPE_STARTER/BUSINESS/GROWTH_PRICE_MONTHLY
RESEND_API_KEYunsetResend API key for transactional email. Best-effort — if unset, sends are skipped and never block a webhook.
EMAIL_FROMMarrow <hello@marrow.so>Sender for transactional email.

For the exhaustive, always-current list (including any newly added keys), see api/.env.example — it is the source of truth this page tracks.

Frontend (web/.env.local for dev, container env for prod)

Section titled “Frontend (web/.env.local for dev, container env for prod)”

These are read at runtime, not build time. The container generates a small /config.js file from these env vars at startup, so the same prebuilt image works in any deployment without rebuilding.

VariableDefaultDescription
MARROW_API_URLhttp://localhost:8000URL the browser uses to reach the API. Must be reachable from end-user browsers.
MARROW_API_KEYunsetIf API_KEY is set on the backend, set this to match.
MARROW_OIDC_ENABLEDunsetSet to true when OIDC is configured on the backend. Enables the /login route. Route protection is handled without middleware — server layouts redirect("/login") on a 401 and the client API helper redirects to the OIDC login endpoint on any 401 (there is deliberately no Next.js middleware).
INTERNAL_API_URLsame as MARROW_API_URLURL Next.js uses for SSR fetches inside the Docker network. Set to http://api:8000 in the prod compose file.

When using docker-compose.prod.yml, both files are replaced by a single root .env. Additional vars used only by the Compose file:

VariableDefaultDescription
MARROW_VERSIONv0.4.0 (compose default)GHCR API image tag. Set to the same git release tag you checked out; update in .env whenever you upgrade. Also labels the locally built web image.
POSTGRES_USERmarrowPostgres username.
POSTGRES_DBmarrowPostgres database name.
POSTGRES_PASSWORDRequired. Postgres password.
API_PORT8000Host port the API binds to.
WEB_PORT3000Host port the web binds to.