Busabase

Production with Compose

The four-service Compose deployment — Postgres, object storage, a one-shot migration job and the app — plus TLS, external databases, and the credential trap that breaks attachment uploads.

The all-in-one image is one container's lifecycle for everything. That is the right trade for a trial and the wrong one for production: you cannot restart the app without restarting the database, and you cannot scale, back up, or upgrade the pieces independently.

The Compose deployment splits it into four:

ServiceWhat it isNotes
postgresPostgres 16Its own named volume
seaweedfsObject storage for attachmentsApache-2.0 licensed, S3-compatible
migrateOne-shot migration jobRuns to completion, then exits
appBusabase itselfWaits for all three above

Why migrations are their own service

migrate runs once and exits; app declares condition: service_completed_successfully on it. This matters as soon as you run more than one app replica: if each replica ran migrations at start-up, they would race, and the loser fails in a way that looks like a corrupt schema rather than a lock conflict.

Setting it up

git clone https://github.com/busabase/busabase-premium.git
cd busabase-premium/compose
./init.sh
BUSABASE_IMAGE=busabase/busabase-premium:latest docker compose up -d

Run init.sh rather than editing .env by hand. The S3 credentials appear in two places — .env for the app and seaweed-s3.json for the storage service — and they must match. Filling them in manually and missing one is the single most common self-inflicted failure here, and its symptom is misleading: the app starts perfectly, signs you in perfectly, and then every attachment upload fails.

init.sh refuses to overwrite an existing .env. That is deliberate — regenerating credentials against a database that already has data locks you out of it.

TLS and reverse proxy

Nothing in the Compose file terminates TLS. Put your own proxy in front of the app port and set APP_URL to the address users actually type:

server {
  listen 443 ssl;
  server_name busabase.company.internal;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Busabase streams agent output and uses websockets for live updates.
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
  }

  client_max_body_size 100m;
}

proxy_buffering off and the Upgrade headers are not optional. With buffering on, agent responses arrive in one lump at the end of the turn instead of streaming, which reads as "the agent hung". client_max_body_size must be at least as large as your largest attachment, or uploads fail at the proxy before Busabase ever sees them.

APP_URL has to match the public address exactly, including scheme and port. Sign-in cookies are issued against it, so a mismatch produces a sign-in that appears to succeed and then bounces straight back to the login page.

Using your own Postgres or S3

Both services in the Compose file exist for convenience, not necessity. To use infrastructure you already operate, point the app at it and remove the local service:

PG_DATABASE_URL=postgresql://user:password@your-host:5432/busabase
STORAGE_URL=minio://ACCESS_KEY:SECRET_KEY@your-s3-host:9000/bucket?auto_create=true&ssl=false

Storage is configured as one URL, not a set of separate variables. The minio:// scheme is an internal tag meaning "S3, path-style addressing, no TLS" — it describes the protocol, not the vendor, and is correct for SeaweedFS, MinIO and anything else S3-compatible. Use ssl=true and drop the port for a TLS endpoint.

If you do this, drop postgres / seaweedfs from depends_on too, or Compose will wait forever for a service it is no longer starting. Keep migrate — it still has to run against whichever database you chose.

Any S3-compatible store works. If yours is behind a proxy that rewrites or strips headers, see the checksum note in Troubleshooting.

Next

On this page