Upgrading
How to move to a newer version, what happens when a migration fails, and why rolling back means restoring a backup rather than downgrading.
Before you start
Take a backup. Not as ceremony — as the actual rollback plan. See below for why there is no other one.
Compose
./backup.sh /var/backups/busabase
docker compose pull
docker compose up -dmigrate runs first, to completion, before the app starts. If it fails, it exits non-zero and the app never starts: condition: service_completed_successfully sees to that. You get a stopped deployment with a clear reason in the logs, not a running one against a half-migrated schema.
docker compose logs migrateThe output names the failing SQL statement and the database error, but not the migration file it came from. To find the file, grep the migrations directory for the statement:
docker compose run --rm --entrypoint sh migrate -c \
"grep -rl 'this_table_does_not_exist' apps/busabase-cloud/src/db/migrations"Send that file with a support bundle rather than hand-patching the schema — a partially applied migration is a state we can fix from the file, and one that is much harder to fix after someone has edited tables directly.
All-in-one
docker pull busabase/busabase-premium-allinone:trial
docker rm -f busabase
docker run -d --name busabase \
-p 3000:3000 \
-v busabase-data:/data \
--restart unless-stopped \
busabase/busabase-premium-allinone:trialThe -v busabase-data:/data is what makes this an upgrade rather than a fresh install. Same volume name, same data. Omit it and you get a brand-new empty instance that looks like your data was deleted.
Migrations run at start-up here too, and a failure is fatal: the container stops instead of serving requests against a schema it does not understand.
Rolling back
There is no downward migration path. A migration that has added a column does not have an inverse that knows what to do with the rows written since.
Rolling back therefore means: run the previous image tag, and restore the backup you took before upgrading. That is the entire procedure, and it is why the backup step is not optional.
BUSABASE_IMAGE=busabase/busabase-premium:<previous-tag> docker compose up -d
# then restore per /docs/self-hosted/backup-restoreChecking it worked
curl -s http://localhost:3000/api/healthThe version field in the response tells you which build is actually serving traffic — worth checking rather than assuming, particularly on a host that had an older image cached.