Named volumes are where your container data actually lives — the database, the uploads, anything that has to survive a container being recreated. They’re managed by Docker and don’t map to a folder you can just copy in Explorer, so backing them up needs a small trick: mount the volume into a throwaway container and archive its contents from there.
This guide covers the whole loop. You’ll back a volume up to a tarball, restore it, and — importantly — learn when a tar copy is the wrong tool and a proper database dump is what you want. Then scheduling backups so they happen on their own, and moving a volume to another host.
Everything uses the standard docker run with a temporary alpine container, so there’s nothing extra to install. The commands are written for a Linux/bash host; on Windows use the equivalent path syntax for the host side.
Why you can’t just copy the folder
On Linux, named volumes do sit on disk, under Docker’s data directory:
/var/lib/docker/volumes/<volume-name>/_data
You can see the exact path for any volume:
docker volume inspect db-data
So why not just cp that folder? Two reasons. First, on Docker Desktop (Windows and Mac) there is no such folder on your machine — the volume lives inside Docker’s Linux VM, out of reach of Explorer or Finder. Second, even on Linux, copying a live volume’s files as root can trip over permissions and catch a database mid-write. Going through a container sidesteps both problems and works identically everywhere. If you want the deeper background on how volumes are stored, the bind mount vs named volume guide covers it.
Back up a volume to a tarball
The core pattern: run a tiny alpine container, mount the volume you want to back up at /data, mount your current host folder at /backup, and tar the contents across.
docker run --rm \
-v db-data:/data \
-v "$PWD":/backup \
alpine tar czf /backup/db-data.tar.gz -C /data .
Reading it left to right: --rm removes the container as soon as it finishes, -v db-data:/data mounts the named volume read side, -v "$PWD":/backup mounts your current directory so the archive lands on the host, and tar czf ... -C /data . archives everything inside the volume. When it’s done you have db-data.tar.gz in your current folder.
That file is portable. Copy it to backup storage, another server, or wherever your retention lives.
Restore a volume from a tarball
Restoring reverses the process: mount the target volume and the backup folder, then extract the archive into the volume.
# Create the target volume first if it doesn't exist
docker volume create db-data
# Extract the backup into it
docker run --rm \
-v db-data:/data \
-v "$PWD":/backup \
alpine sh -c "tar xzf /backup/db-data.tar.gz -C /data"
Restore into a fresh volume when you can. Extracting over a volume that already has data mixes old and new files, which for a database is a recipe for corruption. If you’re restoring to recover, remove or recreate the volume first so you start clean — and make sure no container is using it while you extract.
Databases: dump, don’t just tar
This is the distinction that saves you from a backup that looks fine and fails when you need it. A running database writes to its files constantly. A raw tar of those files can capture them mid-transaction, producing an archive that won’t restore into a working database.
For databases, use the engine’s own dump tool against the running container. That produces a consistent logical backup the engine knows how to read back.
For MySQL or MariaDB:
# Dump all databases to a .sql file on the host
docker exec mysql-container \
sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' \
> backup-$(date +%F).sql
For PostgreSQL:
# Dump every database with pg_dumpall
docker exec -t postgres-container \
pg_dumpall -U postgres > backup-$(date +%F).sql
Restoring a dump feeds the .sql file back into the running database:
# MySQL / MariaDB
docker exec -i mysql-container \
sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < backup-2026-07-19.sql
# PostgreSQL
docker exec -i postgres-container \
psql -U postgres < backup-2026-07-19.sql
File copy (tar) vs database dump — which to use
| Static files, uploads, config | tar method — fast and exact for data that isn't being written. |
|---|---|
| Running database (MySQL/Postgres) | Dump tool (mysqldump / pg_dump) — consistent, restorable. |
| Stopped database | Either works; tar is fine once nothing is writing. |
| Whole-volume migration to a new host | tar method — moves the raw data as-is. |
| Point-in-time DB recovery | Dump — restores into a clean running engine. |
Schedule backups with cron
A backup you have to remember to run isn’t a backup. On a Linux host, put the command in a small script and let cron run it.
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR=/srv/backups
DATE=$(date +%F)
# Tar a static-data volume
docker run --rm \
-v wp-content:/data \
-v "$BACKUP_DIR":/backup \
alpine tar czf "/backup/wp-content-$DATE.tar.gz" -C /data .
# Dump the database properly
docker exec mysql-container \
sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --all-databases' \
> "$BACKUP_DIR/db-$DATE.sql"
# Delete backups older than 14 days
find "$BACKUP_DIR" -type f -mtime +14 -delete
Make it executable and add a cron entry to run it nightly:
chmod +x backup-volumes.sh
# Edit your crontab
crontab -e
# Run the backup every night at 02:30
30 2 * * * /path/to/backup-volumes.sh >> /var/log/docker-backup.log 2>&1
The find ... -mtime +14 -delete line matters as much as the backup itself — without retention cleanup, the backup folder grows until the disk fills, which can take the whole host down. Keep enough history to be useful and prune the rest.
Move a volume to another host
Because named volumes carry no host path, moving one to a different machine is just back-up, copy, restore. On the source host:
docker run --rm \
-v db-data:/data \
-v "$PWD":/backup \
alpine tar czf /backup/db-data.tar.gz -C /data .
Copy the tarball to the new host:
scp db-data.tar.gz user@newhost:/tmp/
Then on the destination host, create the volume and extract into it:
docker volume create db-data
docker run --rm \
-v db-data:/data \
-v /tmp:/backup \
alpine sh -c "tar xzf /backup/db-data.tar.gz -C /data"
Start your container or stack on the new host and it picks up the restored volume. For a database, prefer moving a dump instead of a raw tar, for the same consistency reason as before.
Volume backup checklist
- Identify which volumes hold data you can't regenerate (databases, uploads)
- Use the tar method for static files; use mysqldump/pg_dump for live databases
- Add a date to backup filenames so you keep a history, not one snapshot
- Schedule it with cron and include retention (delete old backups)
- Copy backups off the host so they survive a disk failure
- Test a restore at least once — an untested backup isn't a backup
The down -v reminder
Worth repeating because it’s how most people lose a volume in the first place. docker compose down keeps your named volumes; docker compose down -v deletes them. There’s no recycle bin. If you run down -v on a stack with a database and don’t have a backup, that data is gone. Having the backups above in place is exactly what makes that mistake recoverable instead of fatal — and the Compose beginner guide explains the down vs down -v difference in full.
Wrapping up
Backing up Docker volumes comes down to two patterns and knowing which to use. For static data — uploads, config, generated files — mount the volume into a throwaway alpine container and tar it to the host. For live databases, skip the raw copy and use mysqldump or pg_dump so the backup is consistent and actually restores. Wrap whichever you need in a script, schedule it with cron, add retention, and copy the results off the host.
The one habit that separates a real backup strategy from a false sense of security: test a restore. For more on how volumes work and the storage decisions behind them, see bind mount vs named volume and the rest of the Docker & Containers guides.