Docker Volumes store data independently from containers but require separate approach for backup. This guide describes universal methods for working with volumes using popular services as examples.
๐ก Replace volume names with your own. Methods work with any containers.
๐ฆ Backup volumes
Via docker-volume-backup (recommended)
docker run --rm \
-v portainer_data:/backup/portainer_data \
-v postgres_data:/backup/postgres_data \
-v redis_data:/backup/redis_data \
-v /opt/docker/backup:/archive \
--entrypoint backup \
offen/docker-volume-backup:v2
Parameters:
| Parameter | Description |
|---|---|
-v <volume>:/backup/<name> | Map volume to backup directory |
-v /opt/docker/backup:/archive | Where to save archive on host |
--entrypoint backup | Run backup mode |
--rm | Remove container after completion |
Why this method:
- โ One container - all volumes
- โ Automatic tar.gz compression
- โ No service stop required (for most scenarios)
- โ Suitable for cron automation
Alternative: via tar directly
# Single volume backup
docker run --rm \
-v postgres_data:/volume:ro \
-v /opt/docker/backup:/backup \
alpine tar czf /backup/postgres-$(date +%F).tar.gz -C /volume .
Features:
:ro- read-only, protects from modification during backup- Direct archive creation without intermediate scripts
- Suitable for one-time operations
๐ Restore from backup
Step 1: Extract archive
tar -C /tmp -xvf backup-*.tar.gz
Step 2: Copy data to volumes
# Portainer
docker run -d --name tmp-portainer -v portainer_data:/restore alpine
docker cp /tmp/backup/portainer_data tmp-portainer:/restore
docker stop tmp-portainer && docker rm tmp-portainer
# PostgreSQL
docker run -d --name tmp-postgres -v postgres_data:/restore alpine
docker cp /tmp/backup/postgres_data tmp-postgres:/restore
docker stop tmp-postgres && docker rm tmp-postgres
# Redis
docker run -d --name tmp-redis -v redis_data:/restore alpine
docker cp /tmp/backup/redis_data tmp-redis:/restore
docker stop tmp-redis && docker rm tmp-redis
Why via temporary containers:
- Direct volume access from host is complex (path in
/var/lib/docker/volumes/) docker cppreserves file permissions and attributes- Containers removed after copy - clean and safe
Recommendation
# Stop services before restore
docker compose stop postgres redis portainer
# Perform restore
# Start services
docker compose start
๐ Migrate volumes
Copy to new volume
docker volume create new_volume && \
docker run --rm -it \
-v old_volume:/from \
-v new_volume:/to \
alpine ash -c 'cd /from && cp -av . /to'
Breakdown:
docker volume create- creates target volume-v old_volume:/from- mounts source-v new_volume:/to- mounts destinationcp -av- copies with preserved permissions (-a) and progress (-v)
When to use:
- Renaming volume
- Migrating to different storage driver
- Cleaning from temp files (copy only needed data)
Cross-host migration
# Source host: create archive
docker run --rm \
-v postgres_data:/data \
-v /backup:/archive \
alpine tar czf /archive/postgres.tar.gz -C /data .
# Copy to new host
scp /backup/postgres.tar.gz user@newhost:/backup/
# New host: restore
docker volume create postgres_data
docker run --rm \
-v postgres_data:/data \
-v /backup:/archive \
alpine tar xzf /archive/postgres.tar.gz -C /data
Advantages:
- Transfers all data and permissions
- Independent of Docker versions
- Works over any network (SCP, rsync, HTTPS)
๐ Automation
Via cron on host
# /etc/crontabs/root - daily backup at 03:00
0 3 * * * docker run --rm -v postgres_data:/backup/data -v /opt/backup:/archive --entrypoint backup offen/docker-volume-backup:v2
Via Docker Compose
version: "3.8"
services:
backup:
image: offen/docker-volume-backup:v2
container_name: volume-backup
environment:
BACKUP_CRON_EXPRESSION: "0 3 * * *"
BACKUP_RETENTION_DAYS: "7"
volumes:
- postgres_data:/backup/postgres_data
- redis_data:/backup/redis_data
- /opt/backup:/archive
entrypoint: ["/bin/sh", "-c", "backup"]
Features:
- Built-in backup rotation
- Logging inside container
- S3-compatible storage support
๐ Integrity verification
# After backup: verify archive
tar -tzf backup-*.tar.gz | head -20
sha256sum backup-*.tar.gz > backup.sha256
# After restore: verify data
docker system df -v | grep postgres_data
docker logs postgres --tail 50
โ ๏ธ Common issues
# "volume not found"
โ docker volume ls | grep <name>
โ docker volume inspect <name>
# "permission denied"
โ Run as root or with sudo
โ Check permissions: ls -la /opt/docker/backup/
# Backup too large
โ Exclude cache: tar --exclude='cache/*' ...
โ Configure rotation: BACKUP_RETENTION_DAYS=7
# Restore not working
โ Stop service: docker stop <container>
โ Check volume not in use: docker ps --filter volume=<name>
๐ Checklist
Before backup
- Checked space:
df -h /opt/docker/backup - Stopped DB and critical services
- Tested on one volume
Before restore
- Stopped target containers
- Verified archive:
tar -tzf backup.tar.gz - Prepared rollback plan