How to Back Up and Restore Docker Volumes (and Move a Container to Another Server)

Back up and move Docker apps: Containers are disposable. The data isn't.

Containers are disposable; the data in their volumes is not. If you've followed this series your apps keep data in ./data folders next to their compose files, which makes backup almost trivial — but named volumes, running databases and the question "how do I move all this to a new box?" each have a wrinkle worth knowing.

Quick reference

TaskWhat
Bind mount (./data:/app/data)it's a normal folder — back it up like any other
Named volume (appdata:/app/data)lives in /var/lib/docker/volumes//_data
List volumesdocker volume ls
Consistent backup of a volumestop the container, or use the tar-through-container trick
Databasedump it (mysqldump, pg_dump) — never copy its files while running
Move an appcopy compose folder + data to the new server, docker compose up -d

1. Know which kind you have

docker inspect nextcloud --format '{{json .Mounts}}' | python3 -m json.tool

"Type": "bind" with a Source path under your home folder is a bind mount — back up the folder. "Type": "volume" with a Name is a named volume stored under /var/lib/docker/volumes/.

2. Back up bind mounts

Your whole ~/docker folder, with restic or rsync (tutorials 23 and 55). For strict consistency, stop the stack first:

cd ~/docker/nextcloud && docker compose stop
rsync -a ~/docker/ /mnt/backup/docker/
docker compose start

A few seconds of downtime at 2 am is a price worth paying for a backup you can trust. For apps that must stay up, back up live and accept that a file mid-write might be inconsistent — fine for most things, not for databases.

3. Back up a named volume

Either back up /var/lib/docker/volumes//_data as root, or use a throwaway container to tar it — the portable way:

docker run --rm -v appdata:/data -v /mnt/backup:/backup alpine \
tar -czf /backup/appdata-$(date +%F).tar.gz -C /data .

Restore into a (new, empty) volume the same way:

docker volume create appdata
docker run --rm -v appdata:/data -v /mnt/backup:/backup alpine \
tar -xzf /backup/appdata-2026-10-08.tar.gz -C /data

4. Databases: dump, don't copy

Copying a database's files while it runs gives you a backup that may not open. Dump instead:

docker compose exec db mariadb-dump -uroot -p"$DB_ROOT_PASSWORD" --all-databases | gzip > /mnt/backup/nextcloud-db-$(date +%F).sql.gz
docker compose exec db pg_dumpall -U gitea | gzip > /mnt/backup/gitea-db-$(date +%F).sql.gz

Restore:

zcat nextcloud-db-2026-10-08.sql.gz | docker compose exec -T db mariadb -uroot -p"$DB_ROOT_PASSWORD"

Put the dump line in the nightly script before the file backup runs.

5. Move an app to another server

  1. On the old server: docker compose stop in the app's folder.
  2. Copy the folder (compose file, .env, ./data, dumps) to the new one: rsync -a ~/docker/nextcloud/ john@newserver:~/docker/nextcloud/.
  3. Named volumes: tar and restore as in step 3.
  4. On the new server: docker compose up -d. Same images, same config, same data.
  5. Check ownership — if your UID differs between servers, sudo chown -R the data to the new UID (id).

That's the whole migration. It's the reason to prefer bind mounts in ./data: the app is its folder.

6. Restore onto a fresh server from backup

Install Docker (tutorial 24), restore ~/docker from restic or the backup drive, restore any database dumps, docker compose up -d in each folder. Order rarely matters; each app brings its own database.

Troubleshooting

Restored app starts but data is empty. Paths in the compose file don't match where the data landed, or a named volume was recreated empty. docker inspect the mounts.

"permission denied" after restore. Owner UID changed. ls -ln ./data shows the numeric owner; chown -R to match the container's user.

Database container refuses to start on restored files. You copied live database files. Delete the data folder, start fresh, restore the dump.

Backup of /var/lib/docker is huge. You're including image layers. Back up volumes and compose folders, not the whole directory — images re-download.

Wrapping up

Bind mounts in ./data are folders; named volumes need the tar trick; databases need a dump. Stop the stack for a clean copy if you can. Keep each app's compose file, .env and data together, and moving it to a new server is one rsync and one up -d.

Call us