Multiarch fork of borgmatic-collective/docker-borgmatic running on S6 Overlay. Supports linux/amd64 and linux/arm64.
Includes Borg, Borgmatic, Apprise, and optional Docker CLI for container stop/start hooks.
| Tag | Description |
|---|---|
latest | Latest Borgmatic + Borg |
2.x.x-1.x.x | Specific Borgmatic–Borg version pair |
services:
borgmatic:
image: modem7/borgmatic-dockercontainer_name: borgmatichostname: borgmaticvolumes:
- /home:/mnt/source:ro
- ./data/repository:/mnt/borg-repository
- ./data/borgmatic.d:/etc/borgmatic.d/
- ./data/.config/borg:/root/.config/borg
- ./data/.ssh:/root/.ssh
- ./data/.cache/borg:/root/.cache/borg
- ./data/.state/borgmatic:/root/.local/state/borgmaticenvironment:
- TZ=Europe/London
- BORG_PASSPHRASE=changemerestart: unless-stoppedstop_grace_period: 10mCopy .env.template to .env, fill in your values, then:
docker compose up -dInitialise your Borg repository:
docker exec -it borgmatic borgmatic repo-create --encryption repokey-blake2Run a manual backup to verify everything works:
docker exec -it borgmatic borgmatic --stats -v 1 --filesGenerate a full example config:
docker exec borgmatic borgmatic config generate -d /etc/borgmatic.d/config.yamlArchive naming gotcha: By default borgmatic uses
{hostname}in archive names. Docker container hostnames change on every rebuild, which breaks pruning and compaction. Set an explicit archive name in your config:archive_name_format: 'myhost-{now:%Y-%m-%d-%H%M%S}'
The image ships example configs in /etc/borgmatic.d/ that you can use as a starting point. When you mount your own config directory, place your config there:
| File | Purpose |
|---|---|
config.yaml | Minimal working config for a local repository |
config.yaml.example | Remote repository with SSH and passcommand |
config.full.yaml.example | Annotated reference covering all common options |
before-backup.example | Hook script template run before each backup |
after-backup.example | Hook script template run after each successful backup |
failed-backup.example | Hook script template run on error |
To use the hook scripts, make them executable and reference them in your config:
commands:
- before: everythingrun:
- /etc/borgmatic.d/before-backup
- after: everythingstates: [finish]run:
- /etc/borgmatic.d/after-backup
- after: everythingstates: [fail]run:
- /etc/borgmatic.d/failed-backupBorgmatic expands ${VAR} references inside config files at runtime, which is useful for keeping secrets and paths out of your committed config:
source_directories:
- ${BORG_SOURCE_1}
- ${BORG_SOURCE_2}repositories:
- path: ${BORG_REPO}label: remotePass them via your compose file:
environment:
- BORG_SOURCE_1=/mnt/data
- BORG_SOURCE_2=/mnt/media
- BORG_REPO=user@borg.example.com:myrepoBorgmatic has built-in Healthchecks.io integration. Add your ping URL to your config:
healthchecks:
ping_url: ${BORG_HEALTHCHECK_URL}environment:
- BORG_HEALTHCHECK_URL=https://hc-ping.com/your-uuid-hereBorgmatic will ping on start, success, and failure automatically.
| Variable | Description | Default |
|---|---|---|
TZ | Container timezone | Europe/London |
BORG_PASSPHRASE | Repository encryption passphrase | — |
BORG_PASSPHRASE_FILE | Path to a file containing the passphrase (see Secret files) | — |
BORG_RSH | SSH command for remote repos, e.g. ssh -i /root/.ssh/id_ed25519 -p 50221 | — |
CRON | Cron schedule for borgmatic (see Scheduling) | — |
CRON_COMMAND | Command cron runs | borgmatic-start --stats -v 0 2>&1 |
EXTRA_CRON | Additional cron lines appended verbatim | — |
DOCKERCLI | Set to true to install Docker CLI and Compose at startup | — |
EXTRA_PKGS | Space-separated Alpine packages to install at startup | — |
DEBUG_SECRETS | Set to true or 1 to log secret variable values before/after expansion | — |
Any Borg or borgmatic environment variable is passed through automatically — see the borgmatic and Borg documentation for the full list.
Three modes, checked in this order:
1. CRON environment variable
Standard 5-field cron expression:
environment:
- CRON=0 2 * * *2. crontab.txt file
Mount a file at /etc/borgmatic.d/crontab.txt. Use borgmatic-start (not borgmatic) to keep signal handling working:
0 2 * * * borgmatic-start --stats -v 0 2>&1
3. Built-in default
If neither CRON nor crontab.txt is present, borgmatic runs daily at 01:00.
Extra jobs with EXTRA_CRON:
environment:
- EXTRA_CRON=0 6 * * 0 borgmatic-start --stats -v 0 2>&1Disable cron entirely:
environment:
- CRON=falseThe container uses borgmatic-start as the default cron command. When docker stop sends SIGTERM, borgmatic-start forwards it to borgmatic so an in-progress backup can exit cleanly and release its repository lock rather than being killed mid-run.
Set stop_grace_period to give borgmatic enough time to finish:
services:
borgmatic:
stop_grace_period: 10m# adjust to suit your backup size and speedThe container will print a warning at startup if it detects borgmatic being called directly (instead of borgmatic-start) in the active crontab.
Any BORG_* or YOUR_* environment variable ending in _FILE is read from the named file and exported as the base variable at startup. This is the recommended approach for Docker Swarm secrets or any secret management system that writes files:
environment:
- BORG_PASSPHRASE_FILE=/run/secrets/borg_passphrasesecrets:
- borg_passphraseIf both BORG_PASSPHRASE and BORG_PASSPHRASE_FILE are set, the file value takes precedence.
When running borgmatic manually via docker exec, secrets are already available in the S6 container environment:
docker exec borgmatic sh -c \ 'export BORG_PASSPHRASE=$(cat /run/s6/container_environment/BORG_PASSPHRASE) \ && borgmatic list'Mount a directory to /custom-cont-init.d/ containing .sh scripts. They run after packages are installed but before cron starts, in filename order. Useful for generating SSH keys, importing GPG keys, or any one-time setup:
volumes:
- ./my-init-scripts:/custom-cont-init.d:roScripts run as root. A non-zero exit code logs a warning but does not abort startup.
| Path | Description |
|---|---|
/mnt/source | Data to back up — mount read-only for safety |
/mnt/borg-repository | Local Borg repository |
/etc/borgmatic.d/ | Borgmatic config files and optional crontab.txt |
/root/.config/borg | Borg config and keyfiles — back these up |
/root/.ssh | SSH keys for remote repositories |
/root/.cache/borg | Borg chunk cache (speeds up deduplication) |
/root/.local/state/borgmatic | Borgmatic state for periodic check tracking |
/custom-cont-init.d/ | Custom init scripts (optional) |
Apprise is included and integrates with Telegram, Slack, Discord, email, and many more services.
Add notification hooks to your borgmatic config:
commands:
- after: everythingstates: [fail]run:
- apprise -vv -t "Backup FAILED" -b "Borgmatic error on $(hostname)" \"mailtos://smtp.example.com:587?user=you@example.com&pass=secret&to=you@example.com"Or use borgmatic's native Apprise config (borgmatic 1.8.4+):
apprise:
services:
- url: slack://token@Txxxx/Bxxxx/Cxxxxlabel: slack
- url: mailtos://smtp.example.com:587?user=you@example.com&pass=secret&to=you@example.comlabel: emailstates:
- start
- finish
- failfinish:
title: Backup succeededfail:
title: Backup failedBorgmatic's logs are automatically appended to notification bodies from borgmatic 1.8.9+.
Set DOCKERCLI=true to install Docker CLI and Compose at startup, then mount the Docker socket to allow borgmatic hooks to stop and start other containers:
environment:
- DOCKERCLI=truevolumes:
- /var/run/docker.sock:/var/run/docker.sockRather than hardcoding container names, label the services you want paused during backup and let the scripts discover them automatically:
# in each service that should pause during backup:services:
myapp:
labels:
- backupThe image ships ready-to-use docker-stop.sh and docker-start.sh scripts in data/borgscripts/ — see Example borgscripts for setup details. For quick inline use:
commands:
- before: everythingrun:
- docker ps -q -f "label=backup" | xargs --no-run-if-empty docker container stop -t 60
- after: everythingstates: [finish, fail]run:
- docker compose --project-directory /opt/docker/mystack startUsing states: [finish, fail] on the start command ensures containers are never left stopped, even if the backup fails.
Without Docker CLI, you can still control containers via the socket API directly:
commands:
- before: everythingrun:
- 'echo -ne "POST /v1.41/containers/mycontainer/stop HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc local:/var/run/docker.sock 80 > /dev/null'
- after: everythingstates: [finish, fail]run:
- 'echo -ne "POST /v1.41/containers/mycontainer/start HTTP/1.1\r\nHost: localhost\r\n\r\n" | nc local:/var/run/docker.sock 80 > /dev/null'Use
after: everythingwithstates: [finish, fail]to restart containers whether the backup succeeded or failed.
If a backup target is not always available (removable drive, remote server), use a before: action command with exit code 75 to skip gracefully without triggering error notifications:
commands:
- before: actionwhen: [create]run:
- findmnt /mnt/backup-drive > /dev/null || exit 75See the borgmatic soft failure docs and backup to a removable drive for more.
Mounting a Borg archive requires extra privileges. Use the provided restore compose override:
docker compose downdocker compose -f docker-compose.yml -f docker-compose.restore.yml run borgmaticThe restore compose override sets command: /bin/sh, so the container drops you straight into a shell. If you need to attach to an already-running container instead:
docker exec -it borgmatic /bin/bashOnce inside, mount the archive, copy your files, then clean up:
# list available archivesborg list /mnt/borg-repository
# mount a specific archive (or the whole repo to browse all archives)mkdir -p /mnt/restoreborg mount /mnt/borg-repository::archive-name /mnt/restore
# copy files back to their original location or inspect themcp -a /mnt/restore/path/to/file /original/path/
# unmount and exit when doneborg umount /mnt/restoreexitThe required capabilities (SYS_ADMIN, /dev/fuse, AppArmor/SELinux options) are pre-configured in docker-compose.restore.yml.
If Borg has a stale lock from a previously interrupted backup:
borg break-lock /mnt/borg-repositoryPass borgmatic and its subcommand as the container command. S6 starts, runs
all init scripts, executes borgmatic, then shuts down. The
crond crashed with signal 15 message at the end is expected and harmless —
it is S6 cleanly stopping crond when borgmatic exits.
docker run --rm -it \ -v /home:/mnt/source:ro \ -v ./data/repository:/mnt/borg-repository \ -v ./data/borgmatic.d:/etc/borgmatic.d/ \ -v ./data/.config/borg:/root/.config/borg \ -e BORG_PASSPHRASE=changeme \ modem7/borgmatic-docker \ borgmatic listAll output is timestamped by the S6 logging pipeline and sent to Docker's log driver:
docker logs borgmaticdocker logs -f borgmatic # followBorgmatic can be verbose. Add a log rotation policy to prevent Docker logs from growing unboundedly:
services:
borgmatic:
logging:
driver: localoptions:
max-size: 10mmax-file: "3"Enable debug logging for secret variable expansion:
environment:
- DEBUG_SECRETS=trueHook scripts can live anywhere accessible to the container. A dedicated volume keeps them separate from borgmatic config:
volumes:
- ./data/borgscripts:/borgscripts:rocommands:
- before: everythingrun:
- /borgscripts/docker-stop.sh
- after: everythingstates: [finish, fail]run:
- /borgscripts/docker-start.shMake sure the scripts are executable (chmod +x). The container ships ready-to-use example scripts in data/borgscripts/ — copy them into your own borgscripts/ directory and edit the configuration variables at the top of each file.
The container also ships minimal hook templates in /etc/borgmatic.d/ (before-backup.example, after-backup.example, failed-backup.example).
The following scripts are provided in data/borgscripts/. They all require DOCKERCLI=true and the Docker socket mounted:
environment:
- DOCKERCLI=truevolumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./data/borgscripts:/borgscripts:roStops any container carrying a backup label before borgmatic runs. Using a label rather than hardcoded names means you only need to update your other compose files, not this script, when services change.
Label the services you want paused in their own compose files:
services:
myapp:
labels:
- backupmydb:
labels:
- backupThe script has two configuration variables at the top:
BACKUP_LABEL="backup"# label key to filter containers — change if you use a different label
STOP_TIMEOUT=60 # seconds to wait for graceful shutdown before force-killStarts a compose stack after borgmatic finishes. Uses docker compose start rather than up -d to restart only containers that were previously running — it will not recreate services, pull new images, or start anything that was already stopped before the backup began.
Edit the configuration variable at the top:
COMPOSE_DIR="/opt/docker/mystack"# absolute path to your compose project directoryWire both scripts together in your borgmatic config. Using states: [finish, fail] on the start command ensures containers are never left stopped, even when a backup errors:
commands:
- before: everythingrun:
- /borgscripts/docker-stop.sh
- after: everythingstates: [finish, fail]run:
- /borgscripts/docker-start.shRedis supports live backups via BGSAVE — the container does not need to stop. This script triggers a background save and waits for it to complete. When borgmatic runs immediately after, it backs up the resulting dump.rdb as a regular file.
Configuration variables at the top:
REDIS_CONTAINER="redis"# name or ID of the Redis container
WAIT_TIMEOUT=120 # max seconds to wait for BGSAVE to finishYou need to mount the Redis data directory into the borgmatic container as a source, and include it in your borgmatic config:
# docker-compose.yml (borgmatic service)volumes:
- /opt/docker/mystack/redis/data:/mnt/source/redis:ro# borgmatic configsource_directories:
- /mnt/source/redis # directory containing dump.rdb# borgmatic hookscommands:
- before: everythingrun:
- /borgscripts/redis-backup.shNote: If Redis is configured with
appendonly yes, the AOF file is what matters, not dump.rdb. In that case, stop the container before backup and start it again after — thedocker-stop.sh/docker-start.shpair handles this if the Redis container carries thebackuplabel.
borgmatic supports retrieving secrets from a KeePass database via the {credential keepassxc ...} syntax. Because Alpine's keepassxc package pulls in the full Qt6 GUI stack (~150 MB), this image ships an opt-in shim instead — a lightweight Python script backed by pykeepass that implements the exact CLI interface borgmatic expects.
Mount the init script at startup:
services:
borgmatic:
volumes:
- ./data/borgscripts/init-keepassxc-cli.sh:/custom-cont-init.d/init-keepassxc-cli.sh:ro
- /path/to/your/passwords.kdbx:/etc/borgmatic/passwords.kdbx:ro
- /path/to/your/passwords.keyx:/run/secrets/keepass.keyx:ro # optional key fileConfigure borgmatic:
keepassxc:
database: /etc/borgmatic/passwords.kdbxask_for_password: false # required for unattended/cron backupskey_file: /run/secrets/keepass.keyxencryption_passphrase: "{credential keepassxc /etc/borgmatic/passwords.kdbx MyBorgEntry}"See data/borgscripts/KEEPASSXC-CLI.md for full setup instructions, security notes, and database hook examples.
For PostgreSQL, MariaDB/MySQL, MongoDB, and SQLite, borgmatic has built-in support that dumps the database and includes the dump in the archive — no separate script needed. Example for PostgreSQL:
postgresql_databases:
- name: mydbhostname: dbusername: postgrespassword: ${POSTGRES_PASSWORD}format: customSee the borgmatic database documentation for the full list of supported databases and options.
Mount your compose files read-only into the container as a backup source:
volumes:
- /opt/docker/mystack/docker-compose.yml:/mnt/source/docker/docker-compose.yml:ro
- /opt/docker/mystack/.env:/mnt/source/docker/.env:roThen include /mnt/source/docker in your source_directories. This ensures your Docker stack definition is included in every backup alongside your data.
