Configuration reference
Every environment variable the self-hosted deployment reads — what it does, what happens if you get it wrong, and whether changing it needs a restart.
The all-in-one image generates most credentials on first boot and defaults APP_URL to http://localhost:3000; the Compose deployment has init.sh write your .env, and APP_URL is yours to fill in. The differences are called out below.
APP_URL is the one people get wrong. It must match the browser's address bar exactly — scheme, host and port all count. When it does not, the sign-in request is rejected with 403 Invalid origin and the UI shows Invalid origin plainly — it does not look like a successful sign-in that bounces back to the login page.
Required
| Variable | All-in-one default | Compose | Restart needed | What it does |
|---|---|---|---|---|
BUSABASE_IMAGE | — | busabase/busabase-premium:latest | yes | Which image to run. The public :latest is the same self-hosted build for trial and licensed use; pin a version tag from your own mirror if you prefer. |
APP_URL | http://localhost:3000 | required (init.sh asks) | yes | The address users actually type, scheme and port included. Also the baseline for sign-in origin checks. |
APP_PORT | 3000 | 3000 | yes | Host port published for the app. |
POSTGRES_USER | busabase | busabase | yes | Database user. |
POSTGRES_PASSWORD | generated on first boot | required (init.sh generates) | yes | Database password. Generate it, do not choose it. |
POSTGRES_DB | busabase | busabase | yes | Database name. |
S3_ACCESS_KEY | busabase | busabase | yes | Object-storage key. Must match seaweed-s3.json. |
S3_SECRET_KEY | generated on first boot | required (init.sh generates) | yes | Object-storage secret. Must match seaweed-s3.json. |
S3_BUCKET | busabase | busabase | yes | Bucket name; created automatically. |
BETTER_AUTH_SECRET | generated on first boot | required (init.sh generates) | yes | Signs session cookies. Leaking it lets anyone forge a login. |
Origins and reverse proxies
The sign-in endpoints compare the request's Origin header (scheme + host + port must match exactly) against the trusted list. Trusted origins come from:
APP_URL/BETTER_AUTH_BASE_URL/NEXT_PUBLIC_APP_URL;BETTER_AUTH_ADDITIONAL_ORIGINS(a comma-separated list of origins);
Behind a reverse proxy, APP_URL must equal the address the browser actually visits — not the container's internal address. The proxy must also forward the original host and scheme: proxy_set_header Host $host; and proxy_set_header X-Forwarded-Proto $scheme;. Without those, callbacks and cookies can still go wrong even when APP_URL is set correctly.
Configure the public Origin
The all-in-one image defaults to http://localhost:3000 for local evaluation. For any IP, hostname, non-default port, or reverse proxy, set APP_URL before starting the container. Busabase never trusts an Origin merely because a request supplied it.
- To trust several origins at once (an IP and hostname side by side, or a migration period), use
BETTER_AUTH_ADDITIONAL_ORIGINS. - For a hostname change, configure the new Origin and restart before switching traffic.
Optional
| Variable | Default | Restart needed | What it does |
|---|---|---|---|
BETTER_AUTH_ADDITIONAL_ORIGINS | empty | yes | Comma-separated extra trusted origins. The only switch that trusts several addresses at once. |
IMAGE_REGISTRY | docker.io | yes | Pull the Postgres and SeaweedFS images from an internal mirror instead. |
SYSTEM_ADMIN_EMAIL | admin@example.com | yes | Which account gets the system admin console. |
EMAIL_VERIFICATION_REQUIRED | false (self-hosted) | yes | Set to true only with working mail delivery. If delivery fails, set it back to false and restart; the original administrator can then sign in. |
EMAIL_PROVIDER | resend | yes | Mail provider: resend, postmark, sendgrid, or ses. |
| Provider credentials | empty | yes | Set the matching RESEND_API_KEY, POSTMARK_SERVER_TOKEN, SENDGRID_API_KEY, or AWS credential variables. |
EMAIL_FROM / EMAIL_LOGO_URL | product defaults | yes | Sender identity and optional absolute logo URL used in authentication email. |
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET | empty | yes | Both are required; otherwise the GitHub login button and provider are disabled. |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET | empty | yes | Both are required; otherwise the Google login button and provider are disabled. |
SEAWEEDFS_VOLUME_LIMIT_MB | 1024 | yes | Size at which SeaweedFS rolls to a new volume file. Raise it for large attachment estates. |
Set for you
These are injected by the Compose file or the image. You do not normally set them, but knowing they exist explains behaviour you might otherwise find surprising.
| Variable | Value | Why |
|---|---|---|
BUSABASE_EDITION | self-hosted | Removes billing, the waitlist and the public marketing pages. Read once at boot — deliberately not an admin-toggleable flag, because flipping it back would re-expose the billing surfaces. |
PG_DATABASE_URL | built from the POSTGRES_* values | Point this at your own Postgres to use external infrastructure. |
STORAGE_URL | built from the S3_* values | One URL rather than separate variables. minio:// means "S3, path-style, no TLS" — the protocol, not the vendor. |
REDIS_URL | lmdb:///data/cache | An embedded LMDB cache with in-process pub/sub. A separate Redis buys nothing at single-replica scale, and Redis 7.4+ is RSALv2/SSPL, which carries redistribution constraints we would rather not pass on to you. |
Every variable here needs a restart to take effect. There is no runtime reload: the values are read at boot, which is what makes a running instance's configuration reproducible from its .env alone.
Generated, not configured
The all-in-one image generates its own Postgres password, object-storage keys and session secret on first boot and writes them to /data/.credentials with mode 600. They are not in the image, so two people pulling the same image do not share secrets.
Back that file up. It is the only copy.