Label-driven Docker volume backup daemon, powered by restic
Service labels • Daemon configuration • Multi-host • Manual trigger • Databases • Deploy • Restore
- Containers opt in via labels. At the scheduled time the daemon backs up their named volumes, optionally stopping the container for the duration or running a pre-backup command inside it.
- Backups land in a restic repository: a local Docker volume, S3, or an rclone remote.
| 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 full volume name.
The daemon runs restic in a short-lived worker container named volkeep-worker.
| 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.
Forrcloneremotes, pointVOLKEEP_RESTIC_IMAGEat an image bundling therclonebinary (e.g.tofran/restic-rclone) and configure it withRCLONE_CONFIG_*.
VOLKEEP_RESTIC_IMAGE is pulled once at daemon start.
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>. - Set
VOLKEEP_JITTERto spread concurrent fires.
Run a backup pass on demand:
docker kill -s SIGUSR1 volkeepA live database can be dumped instead of stopped:
volkeep.exec-preruns a command inside the container before its volumes are backed up. A non-zero exit skips the backup.volkeep.volumesmust whitelist the volume receiving the dump.- Wrap the command 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