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.

Updated 3 min read

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.

Still stuck? Real engineers answer tickets around the clock, and the status page shows anything network-wide before you ask.