Every environment variable the server reads at runtime, grouped by concern.
Variables are read at boot — changing one means restarting the app.
This list is complete by construction: a test fails the build when
config/runtime.exs reads a variable that is not documented on this page, so
the reference cannot silently drift from the code.
A few variables refuse to boot on an invalid value rather than falling
back to a default — noted per row. That is deliberate: a typo that silently
disabled a bound or a key would otherwise surface as a bill or a breach, not
an error message.
Signs and encrypts session cookies and tokens. Generate: openssl rand -base64 48
MASTER_SECRETS_KEY
—
prod
Wraps every tenant's data-encryption key (the secrets model). 32 bytes, url-safe base64, no padding: openssl rand 32 \| base64 \| tr '+/' '-_' \| tr -d '='. Lose it and every stored secret is unrecoverable. Boot refuses a malformed value
PUBLIC_URL
—
prod
The externally visible base URL, scheme included. A prod instance refuses to boot without it (or the deprecated FOUNTAIN_DOMAIN) — the old http://localhost:4000 fallback silently put localhost links in every verification email. Builds every link that leaves the app (verification and reset emails, llms.txt) and is passed to every sandbox as FOUNTAIN_BASE_URL. An https:// value also switches on HTTPS redirection, HSTS, and the secure cookie flag — all derived from the scheme
PHX_HOST
host of PUBLIC_URL
—
Bare host for the endpoint URL and the LiveView origin check. Set only if it differs from PUBLIC_URL's host
FOUNTAIN_DOMAIN
—
deprecated
The old combined variable; still honoured as a fallback for both of the above. Prefer PUBLIC_URL / PHX_HOST
PHX_SERVER
true in the shipped image
—
1/true/yes starts the web listener. Release tasks run with PHX_SERVER=false … eval '…' to boot the app without binding the port
Platform token for sprites.dev. The app boots without it, but every conversation fails. Never expose it to tenants — it pays for every sandbox
SPRITES_BASE_URL
https://api.sprites.dev
—
Repoints the sandbox API. Anything else must implement the same contract; there is no bundled alternative
SPRITES_TIMEOUT_MS
30000
—
Bounds every HTTP call to the Sprites API. Long-running commands (package installs, clones) set their own per-call timeouts. Boot refuses a non-positive value
SANDBOX_IDLE_TIMEOUT_MINUTES
60
—
No turn activity for this long and the sandbox is reclaimed — the conversation stays resumable. 0 disables the bound; boot refuses anything that is not a non-negative integer
SANDBOX_MAX_LIFETIME_HOURS
24
—
Absolute sandbox age ceiling, regardless of activity. Same 0-disables and boot-refusal rules
LOG_OUTPUT_BUDGET_MB
50
—
Durable log volume per conversation. Once a conversation has persisted this much sandbox output, one truncation marker is written and further output is discarded (retention bounds age; this bounds rate). Same 0-disables and boot-refusal rules
false closes signup entirely. An open instance on the public internet will be found
REGISTRATION_ALLOWED_EMAIL_DOMAINS
any
—
Comma-separated list; signups outside these domains are refused. Empty means no restriction
UNVERIFIED_PRUNE_AFTER_DAYS
30
—
Accounts that never verified their email are deleted after this many days — they cannot log in, so they are rows, not users. 0 disables the sweep
UNVERIFIED_PRUNE_EXEMPT
—
—
Comma-separated email substrings never pruned (operator or test accounts that deliberately stay unverified)
FIRST_USER_ADMIN
false
—
true: while the instance has no admin, the first account to become verified is promoted to admin, audit-recorded (ADR 0011). Leave off on a multi-tenant deployment — it hands admin to whoever verifies first
Production refuses to boot unless exactly one of the three delivery
options is configured — a silently discarded verification email dead-ends
signup with no visible error. See Email.
Variable
Default
Required
Effect
RESEND_API_KEY
—
one of three
Delivery via Resend
SMTP_HOST
—
one of three
Delivery via any SMTP server
SMTP_PORT
587
—
SMTP port
SMTP_USERNAME
—
—
Omit entirely for an unauthenticated relay
SMTP_PASSWORD
—
—
SMTP_TLS
always
—
STARTTLS by default; never for a relay on a trusted network that does not offer it
EMAIL_DELIVERY
—
one of three
none deliberately disables email. Accounts then self-verify at registration (ADR 0011), but password-reset email cannot be delivered, so a forgotten password is unrecoverable in this mode
EMAIL_FROM
—
when mail is on
The From address. Required whenever a real delivery provider is configured — a prod instance refuses to boot without it, since mail from an unverified domain is rejected anyway. Unused under EMAIL_DELIVERY=none
SUPPORT_EMAIL
—
—
Where "contact support" in account emails (suspension, deletion) points. Unset, the copy names no address
The subscription gate. Off by default — on a self-hosted instance it is a lock with no key. Set true only if you run Fountain commercially with Stripe configured
STRIPE_SECRET_KEY
—
for billing
Stripe API key
STRIPE_WEBHOOK_SECRET
—
for billing
Verifies POST /api/stripe/webhook signatures
STRIPE_PRICE_ID
—
for checkout
The subscription price surfaced by Checkout. Unset with billing enabled, signups get a purely local 14-day trial and a logged warning
STRIPE_PRICE_MONTHLY_CENTS
—
no
Monthly price in cents (e.g. 2900), display-only: feeds the admin billing overview's MRR tile. Unset, the tile shows a placeholder instead of a fabricated number
The identity rendered on /terms and /privacy — the operator's, not the Fountain project's. Set all four or none: a partial set refuses to boot. With none set, the pages return 404 and their links disappear from signup and the footer — unless billing is enabled, in which case the pages stay up with loud {{...}} placeholders until you configure them (an instance charging money should publish terms).
Variable
Default
Required
Effect
LEGAL_ENTITY
—
no
The legal entity operating this instance (e.g. Example Corp Inc.)
LEGAL_CONTACT_EMAIL
—
no
Contact address shown on both pages
LEGAL_JURISDICTION
—
no
Governing law / venue (e.g. the State of Delaware, USA)
Comma-separated CIDRs stepped over when resolving the client IP from X-Forwarded-For. Without it, per-IP rate limits collapse into one bucket keyed on the proxy; over-broad, it lets a client spoof past rate limiting
CHECK_ORIGIN_EXTRA
—
—
Comma-separated extra origins allowed to open a LiveView websocket. Your own host is always included
The private Prometheus listener (/metrics, /health). "" or 0 disables it. Keep it off the public internet
SENTRY_DSN
—
—
Error tracking. Unset, the SDK is inert and nothing leaves the instance. Accepts sentry.io or any Sentry-API-compatible endpoint (GlitchTip)
SENTRY_ENVIRONMENT
the build env
—
Environment tag on reported errors
FOUNTAIN_BUILD_SHA
set by the image build
—
Correlates errors and traces with deploys; also shown in the app footer
OTEL_SERVICE_NAME
fountain
—
Service name on exported traces
OTEL_EXPORTER_OTLP_ENDPOINT
HONEYCOMB_ENDPOINT
—
OTLP (HTTP/protobuf) trace export target
OTEL_EXPORTER_OTLP_HEADERS
—
—
key=val,key=val headers on trace export
HONEYCOMB_ENDPOINT
https://api.honeycomb.io
—
Honeycomb shortcut for the endpoint above
HONEYCOMB_API_KEY
—
—
Honeycomb shortcut: adds the x-honeycomb-team header
Trace export is configured only in production while serving, and is off by
default: spans are exported only when OTEL_EXPORTER_OTLP_ENDPOINT,
HONEYCOMB_ENDPOINT or HONEYCOMB_API_KEY is explicitly set. The OTel SDK
also honours its own standard variables, which take precedence — set
OTEL_TRACES_EXPORTER=otlp or =none to force export on or off regardless
of the above.