For AI agents: a documentation index is available at /llms.txt. A markdown version of this page is available at /guide/self-hosted/operate/backup.md.

Self-hostedOperate

Backup

What to capture, the exact commands, and how to prove a backup is restorable.

A complete backup is four things: the application data on the host, the Docker volumes, logical database dumps, and the compose files plus secrets. Miss any one and the restore is partial.

An untested backup is not a backup

Restore into a scratch instance at least once. Most backup failures are discovered during the first real restore, which is the worst possible time.

Run these on the source server

Every command below runs on the server you are backing up. Confirm container names with docker ps first — they vary with the compose project name.

#1. Application data on the host

The application root outside Docker — typically /data — holds files the containers do not.

bash
mkdir -p /backup/$(date +%F)

tar -czf /backup/$(date +%F)/app-data.tar.gz /data

# Confirm the archive is not empty
tar -tzf /backup/$(date +%F)/app-data.tar.gz | head -20

Also capture host config (/etc/app/) and any cron entries (/etc/cron.d/) — those drive digests and reminders.

#2. Docker volumes

Each volume is archived by mounting it into a throwaway Alpine container.

bash
# Uploaded files and attachments
docker run --rm \
  -v orangescrum-ee_orangescrum-app-files:/data \
  -v /backup/$(date +%F):/backup \
  alpine tar -czf /backup/app-files.tar.gz -C /data .

# Application configuration
docker run --rm \
  -v orangescrum-ee_orangescrum-app-config:/data \
  -v /backup/$(date +%F):/backup \
  alpine tar -czf /backup/app-config.tar.gz -C /data .

# Main database volume
docker run --rm \
  -v orangescrum-ee_orangescrum-postgres-data:/data \
  -v /backup/$(date +%F):/backup \
  alpine tar -czf /backup/postgres-data.tar.gz -C /data .

# Reports (Superset) database volume
docker run --rm \
  -v orangescrum-ee_orangescrum-reports-data:/data \
  -v /backup/$(date +%F):/backup \
  alpine tar -czf /backup/reports-data.tar.gz -C /data .

Check the volume prefix

Compose prefixes volume names with the project name — orangescrum-ee_ above. Confirm with docker volume ls. A wrong prefix produces an archive of an empty volume, and nothing warns you.

File volumes can be captured while the stack runs. Database volumes ideally should not — which is why the next step exists.

#3. Logical database dumps — do not skip

A volume-level copy of a running database can be inconsistent. pg_dump gives you a guaranteed clean, restorable backup. Take both.

bash
# Confirm the container names first
docker ps --format '{{.Names}}' | grep postgres

# Main application database
docker exec orangescrum-ee-orangescrum-postgresdb-1 \
  pg_dump -U orangescrum postgres \
  > /backup/$(date +%F)/main-db.sql

# Reports / Superset database
docker exec orangescrum-ee-orangescrum-reports-db-1 \
  pg_dump -U postgres superset \
  > /backup/$(date +%F)/reports-db.sql

# Verify the dumps are non-empty
ls -lh /backup/$(date +%F)/*.sql

Check the dump size every time

A failed pg_dump still creates the file — just a near-empty one. If your backup suddenly shrinks, that is the signal. Alert on it.

#4. Compose files and secrets

bash
cp docker-compose.yml /backup/$(date +%F)/
cp osreports.Dockerfile /backup/$(date +%F)/   # if present

# Secrets — store this archive encrypted
tar -czf /backup/$(date +%F)/secrets.tar.gz ./secrets/

The secrets archive contains live passwords

Encrypt it at rest and restrict who can read it. Never place it in version control or an unencrypted object store.

#Bundle and ship it off the machine

bash
tar -czf orangescrum-backup-$(date +%F).tar.gz /backup/$(date +%F)/

# Transfer — rsync resumes if interrupted
rsync -avz --progress \
  orangescrum-backup-$(date +%F).tar.gz \
  user@BACKUP_HOST:/backups/

A backup on the same host is not a backup

It does not survive losing the host, which is the scenario you are insuring against.

#What a complete set looks like

FileContents
app-data.tar.gzHost application root (/data)
app-files.tar.gzAttachments volume
app-config.tar.gzConfiguration volume
postgres-data.tar.gzMain database volume
reports-data.tar.gzSuperset database volume
main-db.sqlLogical dump, main database
reports-db.sqlLogical dump, Superset database
secrets.tar.gzService credentials
docker-compose.ymlStack definition
osreports.DockerfileReports image build

#Automate it

lua
0 2 * * * /usr/local/bin/orangescrum-backup.sh >> /var/log/orangescrum-backup.log 2>&1

Then watch the things that actually fail:

🕐Backup freshness

Alert if last night's archive is missing.

📉Backup size

A sudden drop means a dump failed and wrote nothing.

☁️Off-host copy

Confirm the transfer succeeded, not just the archive.

🗓Retention

Keep enough history to survive a problem noticed late.

#Prove it works

Quarterly, restore into a throwaway instance and sign in. It costs an hour and tells you whether your backups are real.

🚚Restore procedure

The full restore, written as a server migration.