Back up Docker volumes offsite
with restic and SFTP

Docker volumes hold the data that makes your containers useful. This guide shows how to back them up with restic over SFTP - encrypted, deduplicated, and offsite. Includes bind mounts, named volumes, and database pre-backup hooks.

The right approach for Docker backups with restic

Docker data lives in a few places depending on how you set things up. Bind mounts are just host directories - restic can back them up directly. Named volumes live under /var/lib/docker/volumes/ on the host. Databases need special handling because backing up live database files produces inconsistent snapshots.

Backing up bind mounts (simplest case)

If your compose file uses bind mounts like ./data:/app/data, you can back up the host path directly:

restic backup /opt/appdata /opt/compose /etc \
  --tag docker \
  --exclude="*/logs" \
  --exclude="*/tmp"

Backing up named volumes

Named volumes live under /var/lib/docker/volumes/. You need root to read them.

# List your volumes
docker volume ls

# Back up all named volumes
sudo restic backup /var/lib/docker/volumes/ \
  --tag docker-volumes \
  --exclude="*/buildkit"

Database pre-backup hook

For Postgres or MySQL, dump to a file before the restic run. This is more reliable than backing up live database files.

#!/bin/bash
set -euo pipefail
source /etc/restic/env

# Dump Postgres
docker exec postgres pg_dumpall -U postgres > /tmp/pg_dump_$(date +%Y%m%d).sql

# Dump MySQL/MariaDB
docker exec mariadb mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" --all-databases > /tmp/mysql_dump_$(date +%Y%m%d).sql

# Now back up everything including the dumps
restic backup /opt/appdata /tmp/pg_dump*.sql /tmp/mysql_dump*.sql \
  --tag docker

# Clean up dumps
rm -f /tmp/pg_dump_*.sql /tmp/mysql_dump_*.sql

# Retention
restic forget --keep-daily 7 --keep-weekly 4 --prune

Full docker-compose + SFTP setup

If you prefer running restic in a container, this compose snippet mounts your data directories and runs a nightly backup:

services:
  restic:
   image: mazzolino/restic:latest
   hostname: docker-host
   restart: unless-stopped
   environment:
   RUN_ON_STARTUP: "true"
   BACKUP_CRON: "0 30 2 * * *"
   RESTIC_REPOSITORY: sftp:vaultuser@vault.servercrate.net:22150:/data
   RESTIC_PASSWORD: your-repository-password
   RESTIC_BACKUP_SOURCES: /data
   RESTIC_FORGET_ARGS: >-
   --keep-daily 7 --keep-weekly 4 --keep-monthly 3 --prune
   volumes:
   - /opt/appdata:/data:ro
   - ~/.ssh:/root/.ssh:ro

Setting up the SFTP target

ServerCrate provides a private SFTP endpoint for Restic. Create a free account, get your vault connection string, and plug it in as RESTIC_REPOSITORY. No egress fees when you restore.

Scheduling Docker backups automatically

For host-level restic backups, use a systemd timer as described in the systemd timer guide. For containerized restic, the mazzolino/restic image handles cron scheduling internally via the BACKUP_CRON environment variable using standard cron syntax.

A critical consideration for Docker backup scheduling: if you are dumping databases before the backup, the dump and the backup need to run sequentially in the right order. In a host script this is straightforward. In a container-based approach, use a custom entrypoint script that runs the dump and then triggers the restic backup.

What to skip in a Docker backup

Some Docker artifacts are large, change constantly, and are cheap to recreate, making them poor candidates for offsite backup:

  • /var/lib/docker/overlay2:Container image layers. Never back these up - they are recreated by pulling images. Can be hundreds of GB.
  • Build caches:Docker buildkit caches in /var/lib/docker/buildkit. Exclude these.
  • Log files:Application logs stored in volumes. Exclude unless audit retention requires them.
  • Temporary files:Any /tmp, /run, or runtime state directories inside volumes.

Focus on the data that would be painful to regenerate: database contents, uploaded user files, configuration that isn't in version control, and SSL certificates.

Verifying Docker volume restores

After restoring a Docker volume, test that the application actually comes up correctly with the restored data before considering the backup complete. For databases, restore the dump file to a test container and verify the schema and row counts look correct. This sounds obvious but is frequently skipped - and the restore is the only part of backup that actually matters. See the restic restore guide for full restore command reference.

FAQ

Common questions.

Use env_file pointing to /etc/restic/secrets.env with RESTIC_PASSWORD and RESTIC_REPOSITORY set inside. Mount the file read-only at 0600 owned by root. Never use environment: in compose, which leaks via docker inspect.
The Restic container runs as root inside but the bind-mounted /data is owned by your host user. Either chown the host directory to match the container's UID or run Restic with user: in compose set to your host UID:GID.
Use a systemd timer on the host that runs docker compose run --rm restic backup. Keeps scheduling outside the container, gives you systemd's logging and failure handling, and lets you upgrade Restic without touching the schedule.
Yes, but use a separate service with a different command. Prune locks the repository for minutes to hours -- you don't want a backup attempt blocked behind prune. Schedule prune weekly via a separate systemd timer.
Set logging.driver: journald in compose so logs go to systemd-journald on the host. From there you can grep with journalctl -u docker-restic and forward to Loki or another aggregator. Default json-file driver fills disk over time.
Get started today

Start backing up Docker offsite today.

Private vault. Flat pricing. No egress fees when you restore.

Cancel anytime. 10 GB free tier never expires. No egress fees.

Next steps
How we protect your data
Zero-knowledge encryption, ZFS isolation, what we log
Who runs ServerCrate
Operating commitments, where data lives, transparency
First backup in 5 min
Sign up, init vault, run your first Restic backup
Try it before you decide

Encrypted Restic vault, free forever

10 GB. No credit card. Setup in 5 minutes. Bitcoin or card when you upgrade.

Start free vault →