Environment variables
Every environment variable Feeblo understands — what each one does, which app reads it, and the defaults — grouped by section, sourced from .reference/.env.example and the Effect configs.
Feeblo is configured entirely through environment variables read at startup. This page is the canonical walkthrough: every group, every variable, who reads it, and the footguns. Values come from .reference/.env.example plus the Effect Config calls that actually consume them.
This page is based on .reference/.env.example, apps/server/src/config.ts, packages/auth/src/config.ts, packages/domain/src/auth/config.ts, packages/domain/src/services/s3-config.ts, packages/transactional/src/config.ts, packages/web-shared/src/lib/runtime-public-env.ts, apps/web/astro.config.mjs, and apps/web/wrangler.jsonc.
Which app reads what
| Variable group | Read by server | Read by web |
|---|---|---|
| Database | ✅ DATABASE_URL | — |
| Auth | ✅ AUTH_ENCRYPTION_KEY + optional providers | ✅ TURNSTILE_SITE_KEY (client) |
| App | ✅ APP_URL, API_URL, APP_ROOT_DOMAIN, NODE_ENV | ✅ APP_URL, API_URL, APP_ROOT_DOMAIN, APP_RELEASE |
| SMTP | ✅ all SMTP_* | — |
| Media | ✅ all MEDIA_UPLOAD_* | — |
The web app reads a much smaller set: only the App variables plus APP_RELEASE and TURNSTILE_SITE_KEY. Its Astro env schema (in astro.config.mjs) marks API_URL, APP_URL, APP_ROOT_DOMAIN as required, APP_RELEASE and TURNSTILE_SITE_KEY as optional. On Cloudflare, wrangler.jsonc lists API_URL, APP_URL, APP_ROOT_DOMAIN as required secrets.
[[DATABASE]]
The only database variable is DATABASE_URL; see the database page for connection behaviour.
Prop
Type
[[AUTH]]
AUTH_ENCRYPTION_KEY is read in three places — packages/auth/src/config.ts (AuthConfig), packages/domain/src/auth/config.ts (VerificationOtpConfig), and implicit session signing — and always as a required value. Keep it identical across restarts — it signs/encrypts sessions, so changing it invalidates every existing session.
Prop
Type
Optional auth providers (not in .env.example)
AuthConfig (packages/auth/src/config.ts) reads several optional values that the example file doesn't list. They're all read through optionalString / Config.option, so leaving them unset simply disables that feature:
Prop
Type
The web app reads the site (public) half of Turnstile separately:
Prop
Type
[[APP]]
These are the only App variables the shipped docker-compose.yml forwards into both server and web. They're required everywhere Feeblo runs.
Prop
Type
URL vs host, and the port-stripping quirk
APP_URL and API_URL are compared against request origins in apps/server/src/index.ts using
new URL(...).hostname. APP_ROOT_DOMAIN, though, is matched against the request hostname after
stripping a single :port suffix — and never URL-parsed. Set it to the bare host
(feeblo.example.com), not https://…. The dev example's APP_ROOT_DOMAIN=localhost:3001 works
because the port is stripped and the NODE_ENV=development branch whitelists localhost.
[[SMTP]]
Transactional email over a single nodemailer SMTP transport. See the email page for what the code actually reads and the variables the example declares but ignores.
Prop
Type
[[MEDIA_UPLOAD]]
S3-compatible object storage for uploads. See the media page for bucket policies, public URL construction, and the R2/MinIO specifics.
Prop
Type
There's one media var not in .env.example: MEDIA_PUBLIC_BASE_URL (optional, string). Set it when the public file URL differs from the S3 API endpoint — see the media page.
Putting it all together
A minimal self-host .env (assuming the compose stack from the Docker guide fully provisions Postgres / Mailpit / MinIO):
# App
APP_URL=https://feeblo.example.com
API_URL=https://api.feeblo.example.com
APP_ROOT_DOMAIN=feeblo.example.com
# Database
DATABASE_URL=postgres://feeblo:change-me@postgres:5432/feeblo
# Auth
AUTH_ENCRYPTION_KEY=generate-a-long-random-string
# SMTP
SMTP_HOST=feeblo-smtp
SMTP_PORT=1025
SMTP_FROM_ADDRESS=noreply@feeblo.example.com
# Media
MEDIA_UPLOAD_REGION=us-east-1
MEDIA_UPLOAD_ENDPOINT=http://feeblo-minio:9002
MEDIA_UPLOAD_ACCESS_KEY_ID=feeblo
MEDIA_UPLOAD_SECRET_ACCESS_KEY=change-me-too
MEDIA_PUBLIC_BUCKET_NAME=feeblo-media-publicDocker setup
Bring up a full Feeblo deployment with the published ghcr.io images plus Postgres, SMTP, and S3-compatible storage wired together in one compose file.
Database
Feeblo needs PostgreSQL. Migrations are bundled into the server image and run automatically on every boot; the db:* scripts cover local development.