Backing Up Docker Volumes on Synology
I used to think Hyper Backup on the docker shared folder meant my containers were covered. Then I tried a restore and got a database that wouldn't start, and another app whose data lived in a named volume under /volume1/@docker/volumes that the job never saw. Containers are disposable; the data they write isn't.
What follows is how I find out where each container really keeps its data, dump databases consistently before the file-level backup, and tie it together with Task Scheduler and Hyper Backup. It assumes DSM 6.2, containers running (ideally from docker-compose files under /volume1/docker), an existing Hyper Backup task, and SSH.
Inventory the mounts
For every running container, list what is mounted and where the source lives:
sudo docker ps -q | xargs sudo docker inspect \
--format '{{.Name}}{{range .Mounts}}{{printf "\n %s -> %s (%s)" .Source .Destination .Type}}{{end}}'
Three cases appear:
bindmounts with a source under/volume1/docker/...: covered by any backup of thedockershared folder. Good.volumemounts with a source under/volume1/@docker/volumes/<name>/_data: invisible to File Station and to Hyper Backup, because@dockeris a system folder. These need to be moved or exported.- No mount at all for a path the application writes to: data lives in the container's writable layer and disappears when the container is recreated. Fix the container definition now.
For named volumes, the durable fix is to convert them to bind mounts: stop the container, copy the data, edit the Compose file to point at /volume1/docker/<app>/data, and start again:
sudo docker-compose -f /volume1/docker/app/docker-compose.yml stop
sudo cp -a /volume1/@docker/volumes/app_data/_data/. /volume1/docker/app/data/
Dump databases before the file backup
Copying /var/lib/mysql or a PostgreSQL data directory while the server is running gives you files from different points in time. Either stop the container for the duration of the backup, or produce a logical dump. I do the dump, because stopping databases every night gets old fast, into a folder Hyper Backup includes. Save this as /volume1/scripts/docker-db-dump.sh:
#!/bin/bash
DEST=/volume1/docker/_dumps
DATE=$(date +%F)
mkdir -p "$DEST"
# MariaDB / MySQL containers
for c in nextcloud-db wiki-db; do
docker exec "$c" sh -c 'exec mysqldump --all-databases --single-transaction --routines --triggers -uroot -p"$MYSQL_ROOT_PASSWORD"' \
| gzip > "$DEST/$c-$DATE.sql.gz"
done
# PostgreSQL container
docker exec gitea-db pg_dumpall -U gitea | gzip > "$DEST/gitea-db-$DATE.sql.gz"
# keep two weeks of dumps
find "$DEST" -name '*.sql.gz' -mtime +14 -delete
The $MYSQL_ROOT_PASSWORD expansion happens inside the container's shell, so the password never appears on the host command line. Test it:
sudo bash /volume1/scripts/docker-db-dump.sh && ls -lh /volume1/docker/_dumps
For applications that are not databases but keep a busy SQLite file (Home Assistant, Sonarr, Plex in a container), a stop-copy-start is the safe route:
docker stop sonarr && cp -a /volume1/docker/sonarr/config /volume1/docker/_dumps/sonarr-config-$(date +%F) && docker start sonarr
Schedule the dump before Hyper Backup
Control Panel → Task Scheduler → Create → Scheduled Task → User-defined script. User root, daily at 01:30, script bash /volume1/scripts/docker-db-dump.sh. Enable "Send run details by email" for abnormal termination.
Then set the Hyper Backup task to start at 02:30 so the dumps are done. In the Hyper Backup task's Backup settings, ensure the docker shared folder is selected and, under the application tab, tick Docker. The Docker application backup captures container configurations (the same JSON as Export in the Docker GUI), which lets you recreate containers you did not define in Compose files.
Also tick "Enable client-side encryption" if the dumps contain anything sensitive and the destination is not under your control.
Btrfs snapshots as a first line
If the docker shared folder is on Btrfs, Snapshot Replication (Package Center) adds cheap point-in-time copies. Snapshot Replication → Snapshots → the docker folder → Settings: hourly snapshots, retention 24 hourly and 7 daily. A snapshot of a running database has the same torn-copy problem as a file backup, but for configuration files, application data and yesterday's _dumps it is the fastest possible restore, and it protects against a bad docker-compose up wiping a folder.
Restore test
Restore is the only real test, and it's the step I skipped for years. Pick a dump and load it into a throwaway container:
sudo docker run --rm -d --name restoretest -e MYSQL_ROOT_PASSWORD=x mariadb:10.4
sleep 20
zcat /volume1/docker/_dumps/nextcloud-db-$(date +%F).sql.gz | sudo docker exec -i restoretest mysql -uroot -px
sudo docker exec restoretest mysql -uroot -px -e "SHOW DATABASES; SELECT COUNT(*) FROM nextcloud.oc_filecache;"
sudo docker rm -f restoretest
Then from Hyper Backup → Restore, browse the latest version and confirm _dumps and each application folder are present with today's timestamps. Check Task Scheduler → View Result for the dump task's exit code.
What went wrong for me
- The dump file is 20 bytes:
mysqldumpfailed and gzip compressed an empty stream. Run thedocker execline by hand; typical output isAccess denied for user 'root'@'localhost', meaning the container was created with a different root password than the current environment shows. docker execfails with "container is not running" for one container in the loop: the script continues but you get no dump. Add|| echo "dump of $c failed" >&2after each command so the task's email tells you.- Hyper Backup reports "Failed to backup Docker" in the application section: a container has a name with characters the exporter cannot handle, or a container references an image no longer present. Check the Hyper Backup log for the container name.
- Restored MariaDB data directory refuses to start with "InnoDB: Database page corruption on disk or a failed file read": you restored a file-level copy taken while the server was running. Use the logical dump instead.
Bind mounts, a nightly dump, snapshots and Hyper Backup off-site. With those in place I can rebuild a container host in an afternoon, and that's my definition of a backup.