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.
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.
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.
# 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.
# 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
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
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
| File | Contents |
|---|---|
app-data.tar.gz | Host application root (/data) |
app-files.tar.gz | Attachments volume |
app-config.tar.gz | Configuration volume |
postgres-data.tar.gz | Main database volume |
reports-data.tar.gz | Superset database volume |
main-db.sql | Logical dump, main database |
reports-db.sql | Logical dump, Superset database |
secrets.tar.gz | Service credentials |
docker-compose.yml | Stack definition |
osreports.Dockerfile | Reports image build |
#Automate it
0 2 * * * /usr/local/bin/orangescrum-backup.sh >> /var/log/orangescrum-backup.log 2>&1
Then watch the things that actually fail:
Alert if last night's archive is missing.
A sudden drop means a dump failed and wrote nothing.
Confirm the transfer succeeded, not just the archive.
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 procedureThe full restore, written as a server migration.