Self-hosted service
offen/docker-volume-backup avatar
offen/docker-volume-backup

offen/docker-volume-backup: recurring Docker volume backups to S3, WebDAV, Azure Blob, Dropbox, Google Drive or SSH

Backup Docker volumes locally or to any S3, WebDAV, Azure Blob Storage, Dropbox, Google Drive or SSH compatible storage

4,026 stars155 forksGoMPL-2.0

At a glance

What is it?
A companion container that archives Docker volumes on a cron schedule, rotates old copies and can encrypt them with GPG. It fits existing compose and Swarm setups, but it is not a database-consistent backup tool on its own.
Who is it for?
Adopt it if you run Docker or Swarm and want volume archives pushed to object storage on a cron schedule without writing your own script. Do not adopt it if you need application-consistent database dumps; the stop-during-backup label halts a container, it does not quiesce a database.
Can I use it commercially?
Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap offen/docker-volume-backup fills between named volumes and object storage

Docker named volumes live under the daemon's storage directory and survive container removal, but nothing in Docker itself copies them anywhere else. The README frames the project as a "lightweight (below 25MB) companion container to an existing Docker setup" that handles "recurring or one-off backups of Docker volumes" to a local directory or to S3, WebDAV, Azure Blob Storage, Dropbox, Google Drive or SSH compatible storage, "or any combination thereof". That audience is specific: people who already run compose or Swarm, who keep state in a named volume rather than a bind mount, and who want the archive pushed off the host without writing and owning a shell script with credential handling, scheduling and retention logic.

The project is written in Go and published under MPL-2.0. The last push to main was on 2026-09-22, and the most recent release listed is v2.49.1 on 2026-09-21. The repository is not archived. It carries its own test directory and a golangci configuration, so the codebase is treated as a maintained one rather than a snippet dump.

What it is not: it is not a filesystem snapshot tool and it does not talk to your database. It archives a volume's contents as seen from inside a container that has that volume mounted. If your application writes to a database file while the archive is being taken, the archive can contain a torn write.

How the backup container reads a volume, archives it and ships it

The mechanism is straightforward once you see the mounts. In the compose example, the volume you want protected is mounted into the backup container read-only at a path under /backup, for instance data:/backup/my-app-backup:ro. The entrypoint of the image is /usr/bin/backup with a -foreground flag, so the container runs the backup binary directly rather than a shell wrapper.

Three optional mounts change the behaviour. Mounting /var/run/docker.sock lets the process stop and restart other containers during the run. Mounting a host directory or volume at /archive keeps a local copy of each backup; the README notes the location inside the container can be overridden with BACKUP_ARCHIVE. And a label on the consuming service, docker-volume-backup.stop-during-backup=true, is what the README describes as the signal that "the container will be stopped during backup to ensure backup integrity", with the note that you can omit the label if stopping is not required.

The Go module list shows how the pieces are assembled: robfig/cron/v3 for scheduling, klauspost/pgzip and klauspost/compress for compression, filippo.io/age and ProtonMail/go-crypto alongside GPG encryption, minio-go for S3-compatible targets, gowebdav for WebDAV, the Azure azblob SDK, the Dropbox SDK, Google's API client, and pkg/sftp for SSH transfer. Notifications go through shoutrrr. Retention is handled in-process: the README states the tool "rotates away old backups if configured".

The data flow is therefore: mount volume read-only, optionally stop the labelled container, read the files, compress, optionally encrypt, write to /archive and to each configured remote, then apply the rotation rule. Everything is driven by environment variables loaded through offen/envconfig, which is why the quickstart points at an env_file rather than a config file.

Installing offen/docker-volume-backup and running a first backup

There is no package to install. The project ships as a Docker image, offen/docker-volume-backup on Docker Hub, and the documentation lives at offen.github.io/docker-volume-backup. The quickstart covers two paths: adding a service to an existing compose file, or running the binary as a one-off through the Docker CLI.

The compose path adds a backup service next to the workload and mounts the volume read-only. This is the shape the README gives, including the label that stops the consumer during the run:

yaml
services:
  volume-consumer:
    build:
      context: ./my-app
    volumes:
      - data:/var/my-app
    labels:
      - docker-volume-backup.stop-during-backup=true

  backup:
    image: offen/docker-volume-backup:latest
    restart: always
    env_file: ./backup.env
    volumes:
      - data:/backup/my-app-backup:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /path/to/local_backups:/archive
volumes:
  data:

The README advises locking the image tag to a release version instead of latest in production, and points at the GitHub releases page for the list. The env_file is where credentials and schedule live; the configuration reference linked from the README is the place to look up each key.

For a single run without a compose file, the README gives this command, which mounts the volume and passes S3 credentials as environment variables:

bash
docker run --rm \
  -v data:/backup/data \
  --env AWS_ACCESS_KEY_ID="<xxx>" \
  --env AWS_SECRET_ACCESS_KEY="<xxx>" \
  --env AWS_S3_BUCKET_NAME="<xxx>" \
  --entrypoint backup \
  offen/docker-volume-backup:v2

After the run completes, the archive appears in the configured destination. If /archive is mounted, a local copy is also written there, which is the quickest way to confirm the tool works before pointing it at remote storage. The README also notes you can pass a --env-file instead of individual --env flags.

Where docker-volume-backup is the wrong tool

The stop-during-backup label is the honest part of the design and also the clearest limitation. Stopping a container gives you a consistent view of its files, but it does not give you an application-consistent backup. A Postgres or MySQL data directory copied while the server is stopped is usually fine only if the shutdown was clean; a volume containing a running database that is not stopped can be archived mid-write, and the resulting files may not open.

The README does not document rollback, and it does not describe a restore command. The archives are files, so recovery means unpacking them and putting the contents back into a volume yourself. Anyone expecting a one-line restore from this tool will be disappointed; the project is the backup half of the pair.

There is also a privilege question. Mounting /var/run/docker.sock gives the container control over the Docker daemon, which is effectively root on the host. The README mentions that you can proxy the socket and provide a location through DOCKER_HOST, which is the safer route, but the quickstart mounts the socket directly and read-only. Read-only on the socket does not remove the capability once the daemon accepts the connection.

Finally, if your state is not in a named volume, this tool has nothing to mount. Bind mounts and data living on the host filesystem fall outside its model, and so do application-level exports that a database client would produce.

How it differs from restic-style backup tools

The obvious alternative people reach for is restic, often run in a sidecar container. The approaches differ in what they store. restic builds a content-addressed repository with deduplication and incremental snapshots, so the second backup of a mostly unchanged volume moves far less data and every snapshot is browsable and restorable by path. docker-volume-backup produces discrete archives per run and rotates old ones away according to your retention setting.

That difference has consequences. With per-run archives, each backup is self-contained and you can copy a single file to a laptop and read it without the tool. With a restic repository, you need restic and the repository password to get anything back, and a corrupted repository is a harder failure to recover from. On the other hand, storing full archives every run costs more space and bandwidth as the volume grows, which is exactly the problem deduplication solves.

The other difference is scope. restic is a general backup program that happens to work well on volumes; docker-volume-backup is a Docker-native scheduler and uploader that happens to compress and encrypt. If you already have a restic workflow, adding this tool duplicates the job. If you have nothing and want the smallest possible thing that mounts a volume and uploads it on a cron schedule, this is closer to that.

Licence, upgrade cost and what a version bump means

The project is distributed under MPL-2.0, a file-level copyleft licence. In practice that means modifications to the project's own source files carry the licence forward, while larger works that combine it with other code are treated differently. The README states the licence and links the LICENSE file in the repository; it does not offer legal guidance, and neither does this article. If you redistribute a modified image, read the licence text rather than a summary.

Upgrade cost is low for most users because the interface is environment variables and mounts. The release cadence visible here is a patch release on 2026-09-21, a minor release on 2026-09-06 and another patch on 2026-06-27, which suggests configuration keys are added rather than renamed. The README's own advice to pin a release tag instead of latest is the practical control: it turns an upgrade into a deliberate change of one line in the compose file, and it makes rollback a matter of putting the old tag back. The README does not document a compatibility policy for environment variables across major versions, so a v2 to v3 move is the point at which you would want to read the release notes rather than assume.

The image itself is built from alpine:3.24 with the Go binary copied in, and the base image version is part of the upgrade surface: a base image bump can change available CA certificates or timezone data even when the backup binary is unchanged.

Editorial conclusion

Adopt it if you run Docker or Swarm and want volume archives pushed to object storage on a cron schedule without writing your own script. Do not adopt it if you need application-consistent database dumps; the stop-during-backup label halts a container, it does not quiesce a database. Before rolling it out, verify that /var/run/docker.sock is mounted read-only, that the image tag is pinned to a release rather than latest, and that a restore from /archive actually loads back into the volume.

Frequently asked questions

How do I take a docker volume backup with offen/docker-volume-backup?

Add a backup service to your compose file, mount the volume you want protected read-only under /backup, and supply configuration through an env_file. For a single run, the README shows running the image with --entrypoint backup and the volume mounted at /backup/data.

Where does Docker save volumes, and does offen/docker-volume-backup need that path?

The project does not read the daemon's storage directory directly. It mounts the named volume into the backup container at a path under /backup, so you never need to know where Docker stores the volume on the host.

Are docker volumes persistent, and why back them up separately?

Named volumes survive container removal, which is why applications keep state in them. Persistence on the host is not the same as a copy elsewhere, and this tool exists to produce that copy on local disk or on S3, WebDAV, Azure Blob Storage, Dropbox, Google Drive or SSH compatible storage.

How do I copy a docker volume without stopping the container?

The README says the docker-volume-backup.stop-during-backup=true label causes the container to be stopped during backup for integrity, and that you can omit the label if stopping is not required. Omitting it means the archive is taken while the application keeps writing.

Official sources

  1. License: MPL-2.0
  2. offen/docker-volume-backup on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/offen-docker-volume-backup.svg)](https://hysenlabs.com/projects/offen-docker-volume-backup)