Docker Backups
Back up the volume without stopping the container
Name your volumes and compose files. Each is archived through a throwaway helper container and streamed, with the config that describes the stack, into a bucket you own.
Backup Jobs
Automated backup schedules
postgres-main nightly
Last run 6h ago
web-01 snapshots
Last run 9h ago
api-prod volumes
Last run 4m ago
staging-db weekly
Last run 3d ago
A second container does the reading
A named volume lives wherever the daemon decided to put it — a location that is not stable across storage drivers, rootless installs or remote hosts. So we ask Docker for the volume rather than guessing at a path.
For each volume in the job, VPS Snaps starts a disposable alpine container with two mounts: your volume at /data, and a run-scoped staging directory at /backup. It tars one into the other and exits. The --rm means it deletes itself on the way out.
Your application container is never signalled, never paused and never restarted. It does not know the backup happened. That is what makes a nightly volume backup something you can actually leave switched on for a production stack.
Once every volume is archived and each compose file has been copied in beside them, a single tar czf - over the staging directory streams straight into a multipart upload to your bucket — hashed as it goes, never written to disk on our side. The staging directory is removed as soon as the upload completes.
Run once per volume, on your host
docker run --rm \
-v myapp_postgres:/data \
-v /tmp/vpssnaps-<runId>:/backup \
alpine tar czf /backup/myapp_postgres.tar.gz -C /data .A docker the SSH user can talk to
The job runs as whoever you connect as. That account needs the docker CLI on its PATH and permission to reach the daemon socket — root, or membership of the docker group.
The alpine image, once
The helper container is plain alpine. The first run pulls it if it is not already on the host; every run after that uses the cached image and pulls nothing.
Room in /tmp for the volumes
Per-volume tarballs are staged in a run-scoped directory under /tmp and deleted once the run finishes successfully. The combined archive is never staged — it is streamed as it is created. A run that fails partway can leave that staging directory behind on the server; it's small, and the next successful run for that job doesn't reuse or depend on it.
What one run actually leaves in your bucket
One object per run, with the pieces still separable inside it. You can restore a single volume without unpacking a stack you did not break.
Volumes stay in their own tarballs rather than being merged, so restoring myapp_uploads after a bad deploy does not mean touching your database volume at all.
Compose files are copied in with their path flattened — /opt/myapp/docker-compose.yml becomes _opt_myapp_docker-compose.yml — so two files with the same basename from different directories cannot collide.
The archive is ordinary gzipped tar. Any machine with tar on it can read the whole thing, today or in five years, with or without a VPS Snaps account.
docker-backup-<jobId>-2026-09-03T02-00-06-118Z.tar.gz
└── vpssnaps-<runId>/
├── myapp_postgres.tar.gz one tarball per named volume
├── myapp_uploads.tar.gz
├── _opt_myapp_docker-compose.yml path flattened, / becomes _
└── container-inspect.json when the job names containersFour things a Docker backup will not do for you
Container backups fail quietly when someone assumes they cover more than they do. Here is the boundary, drawn where the code draws it.
Bind mounts are not volumes
A service declaring - /opt/myapp/data:/data has no named volume for Docker to hand us. Nothing about that path appears in a Docker backup. Those directories belong in a file backup job, which tars paths on the host directly.
Images are not in the archive
We capture the compose file that pins your image tags, not the layers themselves. Recovery pulls from your registry the same way a fresh deploy does — which keeps archives small, and means a deleted upstream tag is your problem to solve before disaster, not after.
A live volume is a live filesystem
Nothing is paused. tar walks the volume while the container keeps writing, so you get a crash-consistent copy — the same thing the container would see after a power cut. Fine for uploads, caches and static content. Not what you want for a database.
A failed volume warns, it does not stop the run
If one volume cannot be archived, the run logs a warning, carries on with the rest, and still uploads. That is deliberate — one bad volume should not cost you the other four — but it does mean a green run can be missing a volume. The warning is in the run log, and the archive listing tells you what actually landed.
Cover the rest with the right job type
A Docker job only ever sees named volumes — bind mounts and a database running inside a container both fall outside it, and neither overlaps with what a Docker job already captures, so pairing job types here doesn't risk archiving the same bytes twice.
Databases inside containers
Tarring a live Postgres volume copies files mid-write. Point a database backup job at the container's host and port instead and get a real dump.
Read moreBind-mounted directories
Host paths mounted straight into a container are ordinary directories. A file backup job archives them with tar, exclude patterns and all.
Read moreHosts with no inbound SSH
The Agent runs the same Docker job from inside your network over an outbound WebSocket, and uploads to your bucket itself.
Read moreBringing a stack back up
Restoring is the run played backwards, using the same alpine helper. Nothing here needs VPS Snaps to be reachable — or to still exist.
Unpack the outer archive
One object per run, holding everything that run collected. Unpacking gives you the run directory with each volume still in its own tarball, so you can restore one volume without touching the others.
aws s3 cp s3://acme-backups/9p2v6x4q/docker-backup-....tar.gz . tar xzf docker-backup-....tar.gzPour each volume back in
The restore is the backup run in reverse: create the empty named volume, then let a throwaway alpine container untar into it. The same trick, the same image, no daemon plugins.
docker volume create myapp_postgres docker run --rm -v myapp_postgres:/data \ -v "$PWD/vpssnaps-<runId>":/backup \ alpine tar xzf /backup/myapp_postgres.tar.gz -C /dataBring the stack up on the captured compose file
Rename the flattened compose file back to docker-compose.yml. Because it pins the same image tags the stack was running, compose pulls what it needs and starts services against volumes that are already full.
mv _opt_myapp_docker-compose.yml docker-compose.yml docker compose up -d
Worth rehearsing on a spare host before you need it. Restoring into a fresh VM proves three separate things at once: that the volumes unpack, that your registry still serves the tags your compose file pins, and that the stack starts against restored data. Test it on a quiet afternoon rather than discovering the answer at 3am.
Powerful features to give you peace of mind
Rest easy knowing your data and your reputation are safe.
- Bring your own storage
- Backups land in your own S3-compatible bucket — Backblaze B2, Wasabi, Cloudflare R2, or plain S3. You hold the keys and the data.
- Snapshots stay with your provider
- Provider snapshots are created through the provider's own API and never leave your account. We store the snapshot ID, not the image.
- Seven providers, one dashboard
- DigitalOcean, Hetzner, Vultr, Linode, AWS EC2, Google Compute Engine and Microsoft Azure — scheduled and reviewed from the same place.
- Schedules that fit your traffic
- Hourly, daily, weekly, monthly, or a fixed interval in minutes — anchored to your timezone, so a 02:00 job stays at 02:00 across a DST change.
- Retention that prunes itself
- Set how many days to keep. Older snapshots and archives are cleaned up after each successful run, so storage bills stay flat.
- Step-by-step run logs
- Every run records what it did, in order, with warnings and errors kept in place — so a failure tells you which step broke.
- Complete run history
- Status, duration, byte size, and what triggered each run, kept per job. Proof the backup ran, long after the night it ran.
- Checksummed on upload
- Every archive is hashed as it streams to your bucket and the checksum is stored with the run, so you can verify what landed.
- Run on demand
- Trigger any job by hand before a migration or a risky deploy, without touching its schedule or its retention window.
- Alerts on five channels
- Email plus Slack, Microsoft Teams, Google Chat, and Discord — on success, on failure, and on a job that missed its schedule entirely.
- Encrypted credentials
- SSH keys, database passwords, and storage secrets are sealed with AES-256-GCM before they touch the database.
- Team access with roles
- Invite your team into a shared workspace as owner, admin, or member, so backups outlive whoever set them up.

“We manage a mix of VPS instances, Docker workloads, and traditional Linux servers, so backups used to be scattered across several different processes. VPS Snaps brought all of that into one dashboard and made it much easier to see what ran, what failed, and what needs attention.”
Point a job at your volumes tonight.
Add a server, list the named volumes and compose paths, pick a schedule. Then read the first run log — it tells you exactly which volumes were archived and how big each one turned out to be.
No credit card required. Cancel anytime.