Back Up a Dockerised App
A nightly pattern for Compose stacks: dump the database, back up volumes off-server with restic, prune old copies, and rehearse the restore.
The universal truth from Backups, RAID, and Data Protection applies double to containers: the container is disposable, the data is not, and it lives in volumes and bind mounts you must back up deliberately. This guide shows a pattern that works for n8n, Nextcloud, WordPress, and most Compose stacks: quiesce or dump, copy off-server with restic, test the restore.
Know where the data is#
cd /opt/myapp
docker compose config | grep -A3 volumes # what the project mounts
docker volume ls
docker volume inspect myapp_data | grep Mountpoint
Bind mounts (./data:/data) live where you put them; named volumes live under /var/lib/docker/volumes/<name>/_data. Databases in containers need a dump, not a file copy, or you back up a torn state.
The backup script#
One script, run nightly by a systemd timer (see Run an App as a systemd Service). It dumps the database, snapshots the volumes with restic to another host or an S3-compatible bucket, and prunes old copies:
#!/usr/bin/env bash
# /usr/local/bin/backup-myapp.sh
set -euo pipefail
export RESTIC_REPOSITORY="sftp:[email protected]:/srv/restic/web1" # or s3:https://s3.example.com/bucket
export RESTIC_PASSWORD_FILE=/root/.restic-pass
cd /opt/myapp
mkdir -p /var/backups/myapp
# 1. database dump (pick the line for your stack)
docker compose exec -T db pg_dump -U app app > /var/backups/myapp/db.sql # PostgreSQL
# docker compose exec -T db mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > /var/backups/myapp/db.sql # MariaDB/MySQL
# 2. files: volumes, bind mounts, the compose file and env
restic backup /opt/myapp /var/backups/myapp /var/lib/docker/volumes/myapp_data \
--exclude '**/cache/**' --tag myapp
# 3. retention
restic forget --tag myapp --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
chmod 700 /usr/local/bin/backup-myapp.sh
restic init # once, after setting the two exports above in your shell
/usr/local/bin/backup-myapp.sh && restic snapshots --tag myapp
Apps that write constantly (Nextcloud during sync, a busy database) benefit from a brief maintenance mode or a docker compose stop app around the dump; ten seconds of downtime at 03:30 is cheaper than a corrupt backup.
Restore#
On a fresh server with Docker installed (see Install Docker and Compose) and restic pointed at the same repository:
restic snapshots --tag myapp
restic restore latest --tag myapp --target / # puts /opt/myapp, volumes, and the dump back
cd /opt/myapp && docker compose up -d db
docker compose exec -T db psql -U app app < /var/backups/myapp/db.sql
docker compose up -d
Then point DNS at the new server (Point a Domain at Your VPS) and let the reverse proxy fetch its certificates. Practise this once on a throwaway server; the first restore should not be the one that matters.
Layer it with the included backup#
Every VPS plan includes an off-node backup and the cloud portal offers snapshots; use them as the whole-server safety net and this script as the versioned, off-site copy of the data you actually care about. Details in Backups, RAID, and Data Protection and Snapshots.
