Label-driven Docker volume backup daemon, powered by restic
Containers opt in via labels. At the scheduled time the daemon backs up their
named volumes, optionally stopping the container for the duration, then prunes
old snapshots. Backups land in a restic repository: a local Docker volume, S3,
or an rclone remote. The daemon runs restic in a short-lived container named
volkeep-worker.
| Label | Default | Purpose |
|---|---|---|
volkeep.enable |
required | true to opt this container in |
volkeep.stop |
false |
Stop the container during backup |
volkeep.exec-pre |
— | Pre-backup command run in the container |
volkeep.volumes |
all named mounts | Comma-separated whitelist |
volkeep.retention-days |
daemon default | Daily snapshots to keep |
name: app
services:
app:
image: app
volumes:
- data:/data:rw
labels:
volkeep.enable: true
volkeep.stop: true
volumes:
data:Bind mounts and anonymous volumes are skipped. Snapshots are tagged with the volume name.
| Env | Default | Description |
|---|---|---|
VOLKEEP_SCHEDULE |
required | Daily fire time HH:MM (daemon TZ) |
VOLKEEP_HOST |
required | Identifier for restic snapshots --host |
RESTIC_REPOSITORY |
required | Restic URI, or volume:<name> (local) |
RESTIC_PASSWORD |
required | Restic repo password |
AWS_* |
— | Forwarded to workers (S3 backends) |
RCLONE_* |
— | Forwarded to workers (rclone backends) |
VOLKEEP_RETENTION_DAYS |
5 |
Daily snapshots to keep |
VOLKEEP_MAX_AGE_DAYS |
0 |
Remove snapshots older than this |
VOLKEEP_CHECK |
true |
Verify repo integrity after each pass |
VOLKEEP_JITTER |
0 |
Random pre-fire delay (e.g. 30m) |
VOLKEEP_RESTIC_IMAGE |
restic/restic |
Worker image |
DOCKER_HOST |
local socket | Override to reach a proxied daemon |
RESTIC_REPOSITORY selects the repository:
- Local —
volume:<name>uses a Docker named volume as the repo, backed by a bind mount or any driver viadriver_opts. - Remote — an S3 or rclone backend URI.
For rclone remotes, point VOLKEEP_RESTIC_IMAGE at an image bundling the
rclone binary (e.g. tofran/restic-rclone) and configure it with
RCLONE_CONFIG_*.
RESTIC_PASSWORD is fixed at repo init. Rotating it later locks you out of
existing snapshots. Use restic key add instead.
VOLKEEP_MAX_AGE_DAYS ages out snapshots of volumes that are no longer backed up,
whether the service is gone or its volume set changed; 0 keeps them forever.
The cutoff must exceed retention window.
By design, each host runs its own daemon and repository. To share a single S3
bucket, give each host a distinct prefix (s3:s3.host.com/bucket/<host>) and
set VOLKEEP_JITTER to spread concurrent fires.
Run a backup pass on demand:
docker kill -s SIGUSR1 volkeepA live database can be dumped instead of stopped: volkeep.exec-pre
runs a command inside the container before its volumes are backed up;
volkeep.volumes must whitelist the volume receiving the dump.
A non-zero exit skips the backup.
Wrap it in /bin/sh -c '...' for redirection or variable expansion.
volkeep needs access to the Docker API. compose.dev.yml
wires it through a socket-proxy and shows the full stack.
The snippets below cover only volkeep's own config.
Local:
name: volkeep
services:
volkeep:
image: ghcr.io/deadnews/volkeep
container_name: volkeep
environment:
VOLKEEP_SCHEDULE: 03:00
VOLKEEP_HOST: ${HOSTNAME:-web-1}
RESTIC_REPOSITORY: volume:volkeep_backup
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
volumes:
backup:Remote:
name: volkeep
services:
volkeep:
image: ghcr.io/deadnews/volkeep
container_name: volkeep
environment:
VOLKEEP_SCHEDULE: 03:00
VOLKEEP_JITTER: 30m
VOLKEEP_HOST: ${HOSTNAME:-web-1}
RESTIC_REPOSITORY: s3:s3.host.com/bucket/${HOSTNAME:-web-1}
RESTIC_PASSWORD: ${RESTIC_PASSWORD}
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}Backups are stored in an ordinary restic repository.
Drive it with any restic command (restore, mount).
See the restic docs.
Local:
alias RESTIC='docker run --rm \
-e RESTIC_PASSWORD \
-v volkeep_backup:/repo \
restic/restic -r /repo'
RESTIC snapshots --tag app_data
RESTIC restore latest --tag app_data --target /tmp/outRemote:
alias RESTIC='docker run --rm \
-e RESTIC_PASSWORD \
-e AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY \
restic/restic -r s3:s3.host.com/bucket/web-1'
RESTIC snapshots --host web-1 --tag app_data
RESTIC restore latest --host web-1 --tag app_data --target /tmp/out