orbotodocs
Self-hosting

Deploying with Coolify

The easy way to self-host orboto - Coolify wires domains, TLS and secrets for you.

Coolify is a popular self-hosted deployment platform. If you already run it (or want the simplest possible orboto install), this is the recommended path: point Coolify at the compose file and it wires domains, TLS certificates and secrets for you.

Prefer full control, or no platform at all? Use the standalone Docker guide - everything Coolify automates here is done by hand there.

The deploy

Coolify auto-populates every SERVICE_* magic variable in docker-compose.yml (credentials, random DB name, public hostnames) and keeps them stable across rebuilds.

⚠️ Volumes are external: true. The three persistent volumes (orboto_postgres_data, orboto_rustfs_data, orboto_backups) are declared external so Coolify cannot recreate them on redeploy (which would silently wipe the database). You must create them once on the host before the first deploy - see the pre-flight step below.

Pre-flight (first-time setup OR migrating from an older Coolify-managed install)

SSH to the Coolify host and run the migration helper:

git clone https://github.com/orboto/orboto /tmp/orboto-setup
cd /tmp/orboto-setup
sudo ./scripts/migrate-to-external-volumes.sh          # existing install - copies from Coolify-managed volumes
# or, for a brand-new install that has never deployed yet:
sudo ./scripts/migrate-to-external-volumes.sh --fresh  # creates empty externals, no copy

The script refuses to run while any orboto container is up; in Coolify, click Stop on the orboto app first, then run it.

After it completes you'll have three named volumes on the host - orboto_postgres_data, orboto_rustfs_data, orboto_backups - that the compose file references. Coolify deploys/redeploys from here on will reuse them.

Actual deploy

  1. Create a new resource → Docker Compose → point it at the orboto repository (branch main) → select docker-compose.yml.
  2. Domains: Coolify auto-assigns a hostname from your wildcard domain for SERVICE_FQDN_WEB_80 (the only public FQDN - the api is reached via /api/* through the web's nginx). Optionally swap in your own custom domain.
  3. Deploy. Database migrations run automatically on every container start - the API image's entrypoint is node dist/db/migrate.js && node dist/index.js, so a redeploy always applies pending migrations before the API accepts traffic. No manual step needed, even on upgrades. If a migration fails the container exits non-zero and Coolify won't route traffic to a half-migrated API.

That's it. Open the web FQDN in a browser and the setup wizard will prompt for the first admin account - see First-run setup.

Upgrade safety. Drizzle migrations are idempotent and additive - existing rows keep their values, NOT NULL columns are introduced with DEFAULT + backfill UPDATE in the same migration. The pg_backup sidecar (see below) plus Coolify's volume persistence mean that rolling back a bad deploy only requires redeploying the previous image tag. Before a major upgrade or before applying a migration from an unfamiliar branch, take a pre-migrate snapshot manually:

docker compose exec -T postgres pg_dump -U "$SERVICE_USER_POSTGRES" -Fc "$POSTGRES_DB" > pre-migrate-$(date -u +%Y%m%dT%H%M%SZ).dump

Post-deploy configuration:

  • Email delivery → log in as admin → System Settings → Email delivery. Pick UseSend (recommended for EU / DSGVO - paste an API key from your UseSend admin UI plus the base URL; self-hostable via Docker / Coolify with AWS SES eu-central-1 underneath), Resend (paste an API key from resend.com), or SMTP (host/port/credentials). Click "Send test" to verify.
  • SENTRY_DSN (Coolify env tab) - ships unhandled exceptions to Sentry
  • IMAGE_TAG (Coolify env tab) - pin to a specific git SHA instead of latest
  • LOG_LEVEL, RATE_LIMIT_MAX, S3_BUCKET, S3_REGION, PG_BACKUP_RETENTION_DAYS (Coolify env tab) - overrides for the shipped defaults

On redeploy, volumes and auto-generated secrets are preserved. To rotate a secret (e.g. SERVICE_PASSWORD_64_JWT), clear the value in Coolify and the next deploy regenerates it - note that rotating JWT_SECRET invalidates all sessions and rotating GIT_ENC_SECRET makes existing encrypted git tokens unreadable.

Troubleshooting

My database is empty after a redeploy. Cause: the persistent volumes weren't created as external before the first deploy, so Coolify recreated them from scratch. Fix: this should not happen if you ran the pre-flight step above first. If it already has, restore from a backup - see Backups. Going forward, always run the pre-flight migration helper before the very first deploy on a Coolify host.

Deploy succeeds but the app shows a blank page or a CORS error. Cause: the API and web hostnames don't match what the containers were built/configured with. Fix: confirm SERVICE_FQDN_WEB_80 and SERVICE_URL_API in the Coolify env tab match the domain you're actually opening in the browser, then redeploy.

A deploy exits non-zero right after "running migrations". Cause: a pending migration failed - Coolify intentionally won't route traffic to a half-migrated API. Fix: check the deployment logs for the actual database error. The previous, working deployment stays live until you fix it and redeploy.

I rotated a secret and now nobody can sign in, or git connections stopped working. Cause: rotating JWT_SECRET invalidates every session by design; rotating GIT_ENC_SECRET makes existing encrypted git access tokens unreadable by design. Fix: for JWT_SECRET, everyone simply signs in again - expected. For GIT_ENC_SECRET, re-create each git connection with a fresh access token.

On this page