Busabase

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 itIf you lose it
DatabaseEverything structural: spaces, records, docs, members, permissions, historyEverything
Object storageAttachments — the actual file bytesEvery record survives with a broken attachment link
Configuration.env, seaweed-s3.jsonYou 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/busabase

It 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/busabase

The 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 -d

If 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:

  1. Sign in works — proves BETTER_AUTH_SECRET came across.
  2. Record counts match the original — proves the database restored fully.
  3. 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.

On this page