Backup and restore
The three things you have to back up, a script that does all three, and a restore drill you should run before you need it.
A Busabase instance is three things on disk, and a backup that misses any one of them is not a backup.
| What is in it | If you lose it | |
|---|---|---|
| Database | Everything structural: spaces, records, docs, members, permissions, history | Everything |
| Object storage | Attachments — the actual file bytes | Every record survives with a broken attachment link |
| Configuration | .env, seaweed-s3.json | You cannot open the database you still have |
The third one catches people out. .env holds the Postgres password and the session secret; the storage config holds the S3 credentials. Restore the data without them and you are looking at files you cannot decrypt a session against.
Backing up
The Compose bundle ships backup.sh, which does all three:
./backup.sh /var/backups/busabaseIt writes a timestamped directory containing database.sql.gz, storage.tar.gz and a copy of the configuration files. A daily cron entry is the intended use:
0 3 * * * /opt/busabase-selfhosted/backup.sh /var/backups/busabaseThe backup contains your database password and session secret. Treat the backup directory with exactly the same care as production — the script sets mode 600 on its output, and you should not loosen it.
Backing up the all-in-one image
Everything lives in one volume, so back the volume up:
docker run --rm -v busabase-data:/data:ro -v "$PWD":/out \
alpine tar czf /out/busabase-data.tar.gz -C /data .That includes /data/.credentials, which is the only copy of the generated secrets.
Restoring
Restore into a new deployment rather than over a running one. If the restore turns out to be bad, you still have the original.
# 1. Bring up a fresh deployment, then stop the app so nothing writes mid-restore
docker compose up -d postgres seaweedfs
docker compose stop app
# 2. Database
gunzip -c database.sql.gz | docker compose exec -T postgres psql -U busabase -d busabase
# 3. Object storage
docker run --rm -v busabase-premium_seaweedfs_data:/data -v "$PWD":/in \
alpine tar xzf /in/storage.tar.gz -C /data
# 4. Configuration, then start
cp .env seaweed-s3.json /opt/busabase-selfhosted/
docker compose up -dIf the restored instance answers on a different address than the original —
a different host, or just a different port — edit APP_URL in the restored
.env to match before starting. Session cookies are issued against it, so
leaving the old value produces a sign-in that appears to work and then bounces
straight back to the login page. Nothing in the logs says why. Hit exactly this
while rehearsing the restore for this release.
Run the drill before you need it
A backup you have never restored is a hypothesis. Run this once, on a spare host, and check three things afterwards:
- Sign in works — proves
BETTER_AUTH_SECRETcame across. - Record counts match the original — proves the database restored fully.
- Open an attachment — proves object storage and its credentials both came across.
Point three is the one that fails. It is also the one that a quick "looks fine" check misses entirely, because every page renders correctly right up until someone clicks a file.