Manage Docker Volumes and Backup Container Data on a Linux VPS

If you run Docker containers on a VPS, you already know that docker rm removes everything inside a container. The database, the uploaded files, the configuration state, gone unless you used a volume. Docker volumes are the only way to keep persistent data alive across container restarts, updates, and full re-deployments. But a volume on the same disk as the container is not a backup. A disk failure, an accidental docker volume prune, or a corrupted overlay filesystem can wipe it without warning. This guide shows you how to manage volumes properly, back them up to a safe location, and restore them on a fresh container, all from the command line on a Linux VPS running Ubuntu 24.04.
Why Docker Volumes Need Their Own Management
A Docker volume lives under /var/lib/docker/volumes/ on the host filesystem. Docker isolates it, but it is still a directory. Many people treat volumes as transparent, they let the container write to them, then forget about them until something breaks. The three problems you will hit are:
- Volume bloat: A container writes logs or cache data into a volume and never rotates them. A 10 GB volume can fill up in weeks.
- Orphan volumes:
docker compose downordocker stack rmdoes not remove volumes by default. Over months of redeploying, you accumulate dead volumes that use disk space. - No backup by default: If the VPS disk fails, the volume fails with it. A snapshot-based VPS backup (like the ones available on a Linux VPS with snapshots and backups) protects the host, but it is a blunt tool, you cannot restore a single volume from a full-disk snapshot without restoring the entire machine.
The solution is to treat volumes as active resources: inspect them, size them, back them up individually, and restore them only when needed.
Step 1, List and Inspect Existing Volumes
Start by seeing what you have. All commands assume root or a user in the docker group.
docker volume ls
This prints every volume on the system. The output shows DRIVER (usually local) and VOLUME NAME. For a volume attached to a running container, inspect it to see mount path and size:
docker volume inspect my_app_data
[
{
"CreatedAt": "2026-03-10T07:00:00Z",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/my_app_data/_data",
"Name": "my_app_data",
"Options": null,
"Scope": "local"
}
]
The Mountpoint is the real directory on the host. You can go there and run du -sh to check size:
sudo du -sh /var/lib/docker/volumes/my_app_data/_data
This is your ground truth. If the volume is a database (PostgreSQL, MySQL), expect the _data directory to contain database files, do not copy those raw files while the database container is running (you will get a corrupted copy). Backup from inside the database or stop the container first.
Step 2, Clean Up Orphan Volumes
Orphan volumes are volumes not used by any container. Docker keeps them on disk because you might attach them later, but most of the time they are garbage.
docker volume ls -f dangling=true
List only dangling volumes. If the list is empty, you are clean. If it shows volumes you recognize from an old stack, remove them:
docker volume rm old_volume_name
To remove all dangling volumes in one command:
docker volume prune -f
Be careful, there is no undo. Run docker volume ls before and after to confirm you did not lose a volume you still need.
备份 Docker 卷而不是容器本身,因为卷数据独立于容器生命周期。
Back up Docker volumes, not the containers themselves, volume data lives independently of the container lifecycle.
Step 3, Manual Volume Backup (Tar Method)
The most reliable way to back up a Docker volume is to create a compressed archive of its mountpoint. For a volume named postgres_data, run this:
docker run --rm -v postgres_data:/source -v $(pwd):/backup alpine tar czf /backup/postgres_data_$(date +%F).tar.gz -C /source .
How this works:
--rmremoves the temporary container after the command finishes.-v postgres_data:/sourcemounts the volume into the container at/source.-v $(pwd):/backupmounts the current host directory ($(pwd)) into the container at/backup, so the archive ends up on your host.alpineis a minimal image that already containstar.tar czf /backup/postgres_data_2026-04-01.tar.gz -C /source .compresses the volume content.
Critical: For a database container, stop the container first or use the database tool (pg_dump, mysqldump) to create a logical backup. A file-level tar of a live database volume is unsafe, the database may be in the middle of a write and the copied files will be inconsistent. For application data (uploads, configs, static files), a live tar is safe as long as you expect slight time-skew.
Step 4, Automated Volume Backup With a Script
Manual backups work for one-time migration. For regular protection, write a cron job that backs up every volume to a remote storage bucket or a different disk.
#!/bin/bash
# /usr/local/bin/docker-volume-backup.sh
BACKUP_DIR="/backup/docker-volumes"
TIMESTAMP=$(date +%F_%H%M)
mkdir -p "$BACKUP_DIR/$TIMESTAMP"
for volume in $(docker volume ls -q); do
echo "Backing up $volume..."
docker run --rm -v ${volume}:/source -v ${BACKUP_DIR}/${TIMESTAMP}:/backup alpine \
tar czf /backup/${volume}.tar.gz -C /source .
done
echo "Backup complete: $BACKUP_DIR/$TIMESTAMP"
Make it executable and test it:
chmod +x /usr/local/bin/docker-volume-backup.sh
sudo /usr/local/bin/docker-volume-backup.sh
Then schedule it via cron (run as root):
sudo crontab -e
# Add this line for a daily backup at 3 AM
0 3 * * * /usr/local/bin/docker-volume-backup.sh
If you rent a VPS for automation workflows, you can also use n8n to trigger this script via an SSH node or a webhook, but the cron approach is simpler and does not depend on another service running.
Step 5, Restore a Volume From Backup
When something fails and you need to restore, the inverse of the backup command works. Assume you have the archive postgres_data_2026-04-01.tar.gz in the current directory.
docker volume create postgres_data_restored
docker run --rm -v postgres_data_restored:/target -v $(pwd):/backup alpine \
tar xzf /backup/postgres_data_2026-04-01.tar.gz -C /target
This creates a new volume named postgres_data_restored and extracts the backup into it. After the restore, you can attach this volume to the same or a different container:
docker run -d --name new_postgres \
-v postgres_data_restored:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=changeme \
postgres:16
The container starts and reads exactly what was in the backup. If the original volume name matters, remove the old one and create it with the same name, or stop the container, remove the old volume, and recreate it.
Step 6, Restore a Database From a Logical Dump
For databases, a logical dump (pg_dump, mysqldump) is safer than the tar approach because it exports a consistent state even while the database is running. Do this instead of the volume-level archive for important databases.
PostgreSQL example
docker exec -t my_postgres pg_dumpall -U postgres > postgres_backup_$(date +%F).sql
To restore:
cat postgres_backup_2026-04-01.sql | docker exec -i my_postgres psql -U postgres
MySQL example
docker exec -t my_mysql mysqldump --all-databases -u root -p"${MYSQL_ROOT_PASSWORD}" > mysql_backup_$(date +%F).sql
Restore with:
cat mysql_backup_2026-04-01.sql | docker exec -i my_mysql mysql -u root -p"${MYSQL_ROOT_PASSWORD}"
Storage for these dumps should not live on the same disk as the container, copy them to a separate location. A VPS with snapshots (like the snapshot feature offered on a monthly billing VPS) can take a full-disk backup periodically, but you will still want per-volume archives for point-in-time recovery of a single application.
Troubleshooting Common Volume Problems
Docker volume prune removed everything I needed
There is no native undo. Check if your VPS provider offers a snapshot rollback, that is your only recovery option. To prevent this, never run docker system prune -a --volumes unless you have just verified every volume is backed up.
Backup tar is much larger than the volume size
Most likely caused by log files or temporary cache inside the volume. Run du -sh /var/lib/docker/volumes/your_volume/_data/* | sort -rh | head to locate the large subdirectories, then configure log rotation (via the application or a tool like logrotate).
Permission errors when restoring onto a new VPS
The backup was created by the root user inside the alpine container (UID 0). The restored volume also has UID 0. If your container runs as a non-root user (e.g. UID 999 for PostgreSQL), it will not be able to write to the volume. Fix the ownership after restore:
docker run --rm -v restored_volume:/target alpine chown -R 999:999 /target
Check the container's Dockerfile or documentation for the correct UID, or inspect the running container: docker exec <container> id.
FAQ
What is the difference between a Docker volume and a bind mount?
A volume is managed by Docker and stored under /var/lib/docker/volumes/. A bind mount maps an arbitrary host path (like /home/user/data) into the container. Volumes are portable across hosts and easier to back up; bind mounts are useful for development but require manual permission management.
Can I back up a Docker volume while the container is running?
For application data (uploads, configs, static files), yes, the tar method in Step 3 works safely. For databases, no. Always use a logical dump (pg_dump, mysqldump) or stop the container before backing up the volume at the filesystem level.
How often should I back up Docker volumes?
It depends on how much data loss you can tolerate. A daily cron backup is a minimum for production databases. Application data with lower churn (uploads, configurations) can be backed up weekly. For critical services, consider hourly logical dumps for databases combined with nightly volume archives.
Does Docker Compose handle volume backup automatically?
No. Docker Compose creates and attaches volumes as defined in the docker-compose.yml file, but it does not include any backup mechanism. You must script the backup yourself, as shown in Step 4.
What is the best way to store volume backups?
On a different disk, a separate VPS, or an object storage bucket (S3-compatible). Storing backups on the same VPS disk protects against accidental docker volume rm but not against hardware failure. Off-site backups (to a second VPS or a remote server) are the only real protection.
Can I migrate Docker volumes between two different VPS servers?
Yes. Back up the volume as a tar archive on server A (docker run --rm ...), copy the .tar.gz to server B via scp or rsync, then restore the volume on server B using the same process. The container running on the new VPS attaches to the restored volume normally.
Related articles
- Manage Docker containers with Portainer on a VPS
- Run multiple sites on one VPS with Docker and Nginx proxy
- Docker networking explained: bridge, host, overlay
Docker 卷管理与容器数据备份要点
Docker 卷是持久化数据的唯一正确方式,但卷本身不是备份。要保护容器数据,必须定期将卷内容打包为 tar 存档并存储到不同磁盘或远程服务器上。数据库容器应使用 pg_dump 或 mysqldump 进行逻辑备份,避免复制实时文件系统产生损坏。恢复时,新建卷并解压备份内容即可挂载到任何容器。养成每周检查和清理孤立卷的习惯,避免磁盘被无用数据填满。


