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

  1. The container runs a scheduler with a cron expression.
  2. At each run it optionally stops labeled containers and runs commands inside them (for example a database dump).
  3. It creates a compressed tar archive of everything mounted under /backup, optionally encrypted.
  4. It restarts the stopped containers, uploads the archive to the configured targets and prunes archives older than the retention period.
  5. 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-backupCustom script
SetupCompose service and environment variablesScript, timer and offsite tool
Storage targetsLocal, S3, WebDAV, SSH, Azure Blob, Dropbox and moreWhatever you integrate
Incremental backupsNoYes, when combined with restic or Borg
Best forSmall and medium volumes, many similar stacksLarge 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.

References

Topics: DockerDocker ComposeDocker volumesoffen/docker-volume-backup