offen/docker-volume-backup is a popular open source container that backs up Docker volumes on a schedule and sends the archives to local storage, S3-compatible buckets, WebDAV, SSH and other targets. If you are searching for an offen docker volume backup setup that works in production, this guide shows how to configure it with Docker Compose, stop containers during the backup, include database dumps, encrypt archives, prune old copies and, most importantly, restore.
The problem it solves
Docker volumes are easy to create and easy to forget. Writing and maintaining your own backup scripts for every stack takes time, and homemade scripts tend to fail silently. Without a scheduled, monitored backup, losing a host or running docker compose down -v by mistake means losing the data for good.
offen/docker-volume-backup turns this into configuration: you add one service to your Compose file, mount the volumes you want to protect, and set a few environment variables.
How offen/docker-volume-backup works
- The container runs a scheduler with a cron expression.
- At each run it optionally stops labeled containers and runs commands inside them (for example a database dump).
- It creates a compressed tar archive of everything mounted under
/backup, optionally encrypted. - It restarts the stopped containers, uploads the archive to the configured targets and prunes archives older than the retention period.
- It can send notifications on failure or success.
Basic Docker Compose setup
services:
app:
image: ghcr.io/example/app:1.4.2
volumes:
- app_data:/var/lib/app
labels:
# stop this container while its volume is archived
- docker-volume-backup.stop-during-backup=true
backup:
image: offen/docker-volume-backup:v2
restart: always
env_file: ./backup.env
volumes:
- app_data:/backup/app_data:ro
- /var/run/docker.sock:/var/run/docker.sock:ro # needed to stop/start containers
- /var/backups/app:/archive # optional local copy
volumes:
app_data:
Pin the major version tag (v2) or an exact release rather than latest, so an upgrade never changes the backup behavior without you knowing. Mounting the Docker socket gives the backup container control over Docker; mount it read-only and only if you use the stop or exec features.
Configuration: schedule, retention and S3 target
# backup.env
BACKUP_CRON_EXPRESSION=0 3 * * *
BACKUP_FILENAME=app-backup-%Y-%m-%dT%H-%M-%S.tar.gz
BACKUP_PRUNING_PREFIX=app-backup-
BACKUP_RETENTION_DAYS=14
AWS_S3_BUCKET_NAME=my-backups
AWS_S3_PATH=docker-host-01
AWS_ENDPOINT=s3.example.com
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
GPG_PASSPHRASE=use-a-long-random-passphrase
NOTIFICATION_URLS=smtp://user:[email protected]:587/[email protected]&[email protected]
NOTIFICATION_LEVEL=error
- BACKUP_CRON_EXPRESSION: when to run, in standard cron syntax.
- BACKUP_RETENTION_DAYS with BACKUP_PRUNING_PREFIX: deletes older archives that start with the prefix, so other files in the bucket are never touched.
- AWS_ENDPOINT: lets you use any S3-compatible provider, not only Amazon S3.
- GPG_PASSPHRASE: encrypts the archive before upload. Store the passphrase in a password manager outside the server: without it, the backups cannot be opened.
- NOTIFICATION_URLS: sends alerts (email, Slack, Telegram and others) so a failing backup does not go unnoticed.
Protect backup.env with chmod 600 and keep it out of Git, because it contains storage credentials.
Databases: dump first, then archive
Stopping a database container during the backup works but causes downtime. A better option is an exec label that runs a dump inside the database container before the archive is created, writing to a volume that the backup container also mounts:
services:
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
- pg_dumps:/tmp/dumps
labels:
- docker-volume-backup.archive-pre=/bin/sh -c 'pg_dump -U app -Fc app > /tmp/dumps/app.dump'
backup:
image: offen/docker-volume-backup:v2
volumes:
- pg_dumps:/backup/pg_dumps:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
env_file: ./backup.env
volumes:
pgdata:
pg_dumps:
The archive then contains a consistent dump instead of the raw database files. Do not mount pgdata into the backup container: copying the data directory of a running database is not reliable. The same idea works for MySQL and MariaDB with mysqldump --single-transaction or mariadb-dump. For details on dumps, read Docker Compose Postgres backup.
Run a backup now and check it
docker compose up -d backup
docker compose exec backup backup # trigger a backup immediately
docker compose logs backup --tail 50
ls -lh /var/backups/app
Do this after every configuration change. Then list the bucket to confirm the upload and the prefix.
Restore from an offen/docker-volume-backup archive
# 1. Stop the containers that use the volume
docker compose stop app
# 2. Get the archive (from /archive or the bucket) and decrypt it if needed
gpg -o backup.tar.gz -d app-backup-2026-10-25T03-00-00.tar.gz.gpg
# 3. Extract it
mkdir -p /tmp/restore && tar -C /tmp/restore -xzf backup.tar.gz
ls /tmp/restore/backup # one folder per mounted volume
# 4. Copy the data into the volume with a temporary container
docker run --rm \
-v myproject_app_data:/target \
-v /tmp/restore/backup/app_data:/source:ro \
alpine sh -c "rm -rf /target/* && cp -a /source/. /target/"
# 5. Start the application again
docker compose start app
Compose prefixes volume names with the project name (myproject_app_data); check the exact name with docker volume ls. To restore a database, start the database container and load the dump with pg_restore, as shown in Docker Compose Postgres: restore a dump.
Limits to keep in mind
- Each run creates a full archive. There is no deduplication or incremental transfer, so large volumes mean long uploads and more storage. For hundreds of gigabytes, a deduplicating tool such as restic or Borg is more efficient.
- The backup runs on the same host it protects. If the host is down, monitoring must come from outside.
- Stopping containers causes short downtime: schedule it outside business hours.
- The bucket credentials can usually delete objects. Use object lock or a separate retention policy on the bucket so an attacker on the host cannot erase every copy.
offen/docker-volume-backup vs a custom script
| offen/docker-volume-backup | Custom script | |
|---|---|---|
| Setup | Compose service and environment variables | Script, timer and offsite tool |
| Storage targets | Local, S3, WebDAV, SSH, Azure Blob, Dropbox and more | Whatever you integrate |
| Incremental backups | No | Yes, when combined with restic or Borg |
| Best for | Small and medium volumes, many similar stacks | Large data, full control |
If you want to build it yourself, see Docker backup volume data: automate volume backups with a script.
Backnix is being built as a managed alternative: encrypted, incremental offsite backups of Docker volumes and databases with monitoring outside your host. See the Docker backup page for the current status and the plans.