How to Self-Host Borgmatic with Docker
What Is Borgmatic?
Borgmatic is a wrapper around BorgBackup that replaces manual borg commands with a single YAML configuration file and built-in scheduling. Where BorgBackup gives you a powerful deduplication and encryption engine, borgmatic gives you automated, scheduled, hands-off backups that run from a Docker container with cron built in. It supports local repositories, remote SSH/SFTP destinations, and S3-compatible or Backblaze B2 storage via rclone integration. If you are paying for CrashPlan, Backblaze, or any managed backup service, borgmatic lets you self-host the same functionality with full control over encryption keys and storage destinations.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker and Docker Compose installed (guide)
- 1 GB of free RAM (minimum; more for large backup sets)
- Storage for backup repositories (local disk, NAS, or remote server)
- SSH key pair (if using remote backup destinations)
- A domain name (optional, for monitoring integrations)
Docker Compose Configuration
Create a directory for your borgmatic stack:
mkdir -p /opt/borgmatic
cd /opt/borgmatic
Create a docker-compose.yml file:
services:
borgmatic:
image: ghcr.io/borgmatic-collective/borgmatic:2.1.3
container_name: borgmatic
restart: unless-stopped
init: true # Ensures proper signal handling for cron and borg processes
environment:
# Timezone for cron scheduling
TZ: UTC
# Borg passphrase for repository encryption
# CHANGE THIS to a strong, unique passphrase and store it securely
BORG_PASSPHRASE: "your-strong-passphrase-change-me"
volumes:
# Borgmatic configuration
- ./config:/etc/borgmatic
# Data to back up (mount as many source directories as needed)
- /home:/mnt/source/home:ro
- /opt:/mnt/source/opt:ro
# Local backup repository (skip if using remote-only)
- /mnt/backups/borg-repo:/mnt/borg-repository
# Borg internal state (cache, keys, security)
- borg-config:/root/.config/borg
- borg-cache:/root/.cache/borg
# Borgmatic state (database dump staging, etc.)
- borgmatic-state:/root/.borgmatic
# SSH keys for remote backup destinations
- ./ssh:/root/.ssh:ro
# Crontab for scheduling
- ./crontab.txt:/etc/borgmatic/crontab.txt
# Optional healthcheck — verifies borgmatic can read its config
healthcheck:
test: ["CMD", "borgmatic", "config", "validate"]
interval: 1h
timeout: 30s
retries: 3
start_period: 30s
volumes:
borg-config:
borg-cache:
borgmatic-state:
Create the crontab file that controls backup scheduling:
mkdir -p /opt/borgmatic/config
Create crontab.txt:
# Run borgmatic every day at 3:00 AM UTC
0 3 * * * PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic --verbosity -2 --syslog-verbosity 1
The container’s entrypoint installs this crontab and starts crond automatically. Adjust the schedule to match your backup window.
Configuration File
Create config/config.yaml — this is the core borgmatic configuration:
# /opt/borgmatic/config/config.yaml
# Borgmatic configuration — full reference: https://torsion.org/borgmatic/docs/reference/configuration/
# Directories to back up
source_directories:
- /mnt/source/home
- /mnt/source/opt
# Backup repositories — local and/or remote
repositories:
# Local repository
- path: /mnt/borg-repository
label: local
# Remote repository via SSH (uncomment and configure)
# - path: ssh://[email protected]/./borgmatic-repo
# label: remote-ssh
# Files and directories to exclude from backups
exclude_patterns:
- "*.pyc"
- "*/.cache/*"
- "*/node_modules/*"
- "*/.tmp/*"
- "*/lost+found"
# Exclude caches marked with CACHEDIR.TAG
exclude_caches: true
# Encryption — MUST match what you used during borg init
encryption_passcommand: "printenv BORG_PASSPHRASE"
# Compression — lz4 is fast with decent ratios; zstd offers better compression
compression: auto,lz4
# Retention policy — how many backups to keep
keep_daily: 7
keep_weekly: 4
keep_monthly: 6
keep_yearly: 1
# Consistency checks — run periodically to verify backup integrity
checks:
- name: repository
frequency: 2 weeks
- name: archives
frequency: 4 weeks
# Archive name template
archive_name_format: "{hostname}-{now:%Y-%m-%dT%H:%M:%S}"
This configuration backs up /mnt/source/home and /mnt/source/opt (which map to the host’s /home and /opt) into a local borg repository, keeps 7 daily, 4 weekly, 6 monthly, and 1 yearly archive, and runs integrity checks periodically.
Initial Setup
Before borgmatic can run scheduled backups, you must initialize the borg repository.
1. Start the Container
cd /opt/borgmatic
docker compose up -d
2. Initialize the Repository
docker compose exec borgmatic borgmatic init --encryption repokey-blake2
The repokey-blake2 encryption mode stores the encryption key inside the repository, protected by your passphrase. This is the recommended mode for most setups. The BORG_PASSPHRASE environment variable in your Compose file provides the passphrase automatically.
3. Export the Encryption Key
This step is critical. Without both the key and the passphrase, your backups are permanently unrecoverable.
docker compose exec borgmatic borg key export /mnt/borg-repository > /opt/borgmatic/borg-key-backup.txt
Store borg-key-backup.txt in a separate, secure location — a password manager, a USB drive in a safe, or a different server entirely. Do not store it alongside the backup repository.
4. Run the First Backup
docker compose exec borgmatic borgmatic --verbosity 1
The first backup takes the longest because every file chunk is new. Subsequent backups are fast thanks to deduplication — only changed chunks are stored.
5. Verify the Backup
# List all archives
docker compose exec borgmatic borgmatic list
# Show details of the latest archive
docker compose exec borgmatic borgmatic info --archive latest
Once you see a successful archive listed, borgmatic is working. The cron job in the container handles everything from here.
Configuration
Retention Policies
Borgmatic’s retention settings control how many archives to keep. When borgmatic runs, it creates a new archive and then prunes old ones according to these rules:
# Keep the most recent N archives per time period
keep_daily: 7 # One per day for the last 7 days
keep_weekly: 4 # One per week for the last 4 weeks
keep_monthly: 6 # One per month for the last 6 months
keep_yearly: 1 # One per year for the last 1 year
Borg deduplication means keeping more archives costs very little extra space — most data is shared across archives. Err on the side of keeping more.
Multiple Repositories
You can back up to multiple destinations simultaneously. Borgmatic runs the backup against each repository in sequence:
repositories:
- path: /mnt/borg-repository
label: local
- path: ssh://[email protected]/./borgmatic
label: nas
- path: ssh://[email protected]/./borgmatic
label: offsite
This follows the 3-2-1 backup rule: three copies, two different media types, one offsite.
Exclude Patterns
Fine-tune what gets backed up:
exclude_patterns:
- "*.pyc"
- "*/.cache/*"
- "*/node_modules/*"
- "*.tmp"
- "*/.Trash-*"
- "*/lost+found"
# Exclude large files over a threshold
exclude_if_present:
- .nobackup
# Exclude caches marked with CACHEDIR.TAG
exclude_caches: true
Create an empty .nobackup file in any directory you want excluded. This is useful for temporary build directories or large media collections you back up separately.
Error Handling (v2.x)
Borgmatic v2 changed how Borg warnings and missing sources are handled:
# Source directories MUST exist by default in v2.1.3+
# Set to false if backup sources might be temporarily unmounted
source_directories_must_exist: true
# Most Borg warnings are now treated as errors
# Override specific exit codes if needed:
borg_exit_codes:
- code: 107 # file changed while reading
treat_as: warning
If your backups previously completed with warnings (missing files, permission issues on a few paths), they will now fail by default. Use borg_exit_codes to downgrade specific exit codes back to warnings.
Command Hooks
Borgmatic v2 uses a commands: list for pre/post-backup hooks, replacing the old before_backup/after_backup/on_error format:
commands:
- before: action
when: [create]
run:
- echo "Starting backup at $(date)"
- after: action
when: [create]
run:
- echo "Backup finished at $(date)"
- after: error
run:
- echo "Backup FAILED at $(date)"
Key difference from v1.x: after hooks now run even if an error occurred in the corresponding before hook. This enables proper cleanup (e.g., unmounting filesystems). The when: filter restricts hooks to specific actions (create, prune, check, etc.).
Hooks are the foundation for database dumps, notifications, and healthcheck pings — covered in the Advanced Configuration section.
Advanced Configuration
Remote SSH Backups
For remote repositories over SSH, set up key-based authentication:
# Generate an SSH key pair (if you don't have one)
ssh-keygen -t ed25519 -f /opt/borgmatic/ssh/id_ed25519 -N ""
# Copy the public key to the remote server
ssh-copy-id -i /opt/borgmatic/ssh/id_ed25519.pub [email protected]
Create ssh/config to avoid host key verification prompts:
Host backup-server.example.com
IdentityFile /root/.ssh/id_ed25519
StrictHostKeyChecking accept-new
Set correct permissions:
chmod 700 /opt/borgmatic/ssh
chmod 600 /opt/borgmatic/ssh/id_ed25519
chmod 644 /opt/borgmatic/ssh/id_ed25519.pub
chmod 644 /opt/borgmatic/ssh/config
Then add the remote repository in config.yaml:
repositories:
- path: /mnt/borg-repository
label: local
- path: ssh://[email protected]/./borgmatic-repo
label: remote-ssh
Initialize the remote repository:
docker compose exec borgmatic borgmatic init --encryption repokey-blake2 --repository remote-ssh
S3 and B2 Storage via Rclone
Borgmatic does not natively support S3 or B2, but you can use rclone to mount cloud storage as a local filesystem and then point borg at it. A simpler approach is to use borgmatic’s after_backup hook to sync with rclone:
after_backup:
- rclone sync /mnt/borg-repository remote:borgmatic-backup --transfers 4
For this to work, install rclone in a custom Docker image or use a sidecar container:
services:
borgmatic:
image: ghcr.io/borgmatic-collective/borgmatic:2.1.3
# ... (same as above)
rclone-sync:
image: rclone/rclone:1.69
container_name: borgmatic-rclone
restart: "no"
volumes:
- /mnt/backups/borg-repo:/mnt/borg-repository:ro
- ./rclone.conf:/config/rclone/rclone.conf:ro
# Run after borgmatic completes via cron or manual trigger
entrypoint: ["rclone", "sync", "/mnt/borg-repository", "remote:borgmatic-backup", "--transfers", "4"]
profiles:
- sync # Only runs when explicitly started
Trigger the sync after a backup:
docker compose run --rm rclone-sync
Apprise Notifications
Borgmatic has built-in support for Apprise, which sends notifications to dozens of services (Slack, Discord, Telegram, email, Gotify, ntfy, and more).
Add to config.yaml:
apprise:
services:
# Discord webhook
- url: "discord://webhook_id/webhook_token"
label: discord
# Telegram bot
- url: "tgram://bot_token/chat_id"
label: telegram
# ntfy (self-hosted)
- url: "ntfy://ntfy.example.com/borgmatic"
label: ntfy
send_logs: true # v2.1+ defaults to false for security
start:
title: "Borgmatic: Backup Starting"
body: "Backup job started on {hostname}"
finish:
title: "Borgmatic: Backup Complete"
body: "Backup finished successfully on {hostname}"
fail:
title: "Borgmatic: Backup FAILED"
body: "Backup failed on {hostname}. Check logs."
Healthchecks.io Integration
Send pings to healthchecks.io (or a self-hosted instance) to monitor that backups are running on schedule. If a ping is missed, you get alerted.
healthchecks:
ping_url: "https://hc-ping.com/your-uuid-here"
send_logs: true # v2.1+ disables log sending by default for security
Borgmatic pings the URL at the start of a backup, on success, and on failure. If your backup cron stops running entirely, healthchecks.io notices the missing ping and alerts you. Note: as of borgmatic v2.1, send_logs defaults to false on all monitoring hooks (Healthchecks, Apprise, PagerDuty, Loki) to prevent accidental exposure of log data. Set send_logs: true explicitly if you want backup logs included in pings.
Database Backup Hooks
Use before_backup hooks to dump databases before the filesystem backup runs. This ensures you have consistent database snapshots in every archive:
postgresql_databases:
- name: all
hostname: postgres
port: 5432
username: postgres
password: "your-db-password"
format: custom
mysql_databases:
- name: all
hostname: mariadb
port: 3306
username: root
password: "your-db-password"
# Dumps are stored in /root/.borgmatic/ and included in the backup automatically
For services on the same Docker network, use the container name as the hostname. Add the database containers’ network to borgmatic’s Compose config:
services:
borgmatic:
# ... (same as above)
networks:
- default
- app-network # Network where your database containers run
networks:
app-network:
external: true
Custom Cron Schedule
Edit crontab.txt for different schedules:
# Every 6 hours
0 */6 * * * PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic --verbosity -2 --syslog-verbosity 1
# Twice daily at 2 AM and 2 PM
0 2,14 * * * PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic --verbosity -2 --syslog-verbosity 1
# Every hour (for critical data)
0 * * * * PATH=$PATH:/usr/local/bin /usr/local/bin/borgmatic --verbosity -2 --syslog-verbosity 1
After editing, restart the container to apply:
docker compose restart borgmatic
Backup
Borgmatic is your backup tool, but borgmatic itself needs backing up too. The critical items:
- Borg encryption key — export with
borg key exportand store separately. Without it, all backups are permanently lost. - Borgmatic config (
config/config.yaml) — your entire backup configuration. - SSH keys (
ssh/directory) — needed for remote repository access. - Passphrase — store in a password manager. Not recoverable if lost.
- Crontab (
crontab.txt) — your backup schedule.
Store these in a different location from your borg repository. A password manager entry with the passphrase, a key export, and a copy of config.yaml is a solid approach. If you lose the server, you can reconstruct the entire borgmatic setup from these files plus any surviving borg repository.
# Export the key (run periodically, especially after key changes)
docker compose exec borgmatic borg key export /mnt/borg-repository > /opt/borgmatic/borg-key-backup.txt
# Back up the entire borgmatic config directory
tar czf borgmatic-config-backup.tar.gz /opt/borgmatic/config /opt/borgmatic/ssh /opt/borgmatic/crontab.txt /opt/borgmatic/docker-compose.yml
Troubleshooting
Repository Lock Error
Symptom: Failed to create/acquire the lock or Lock: borg-backup is already running
Fix: A previous borg process was killed or crashed without releasing the lock. Break the lock manually:
docker compose exec borgmatic borg break-lock /mnt/borg-repository
If using a remote repository:
docker compose exec borgmatic borg break-lock ssh://[email protected]/./borgmatic-repo
Only break the lock if you are certain no other borg process is running against that repository.
Lost Passphrase
Symptom: passphrase supplied in BORG_PASSPHRASE, by BORG_PASSCOMMAND or via BORG_PASSPHRASE_FD is incorrect
Fix: There is no fix. Borg encryption is not recoverable without the correct passphrase. If you have a key export file but forgot the passphrase, the backups are lost. This is by design — it is the same property that protects your data from unauthorized access.
Prevention: Store your passphrase in a password manager immediately after creating the repository. Test it periodically by running borgmatic list from a fresh environment.
Slow First Backup
Symptom: The initial backup takes hours or seems stuck.
Fix: This is normal. The first backup must read, chunk, compress, and encrypt every file. Subsequent backups are dramatically faster because borg only processes changed chunks. Monitor progress with:
docker compose exec borgmatic borgmatic --verbosity 2
For very large datasets (multiple TB), consider doing the first backup to a local repository, then using borg transfer or rsync to seed a remote repository.
Permission Denied Errors
Symptom: Permission denied when reading source directories or writing to the repository.
Fix: The borgmatic container runs as root by default, so permission issues usually involve mount-level restrictions. Check:
# Verify mounts are accessible inside the container
docker compose exec borgmatic ls -la /mnt/source/
docker compose exec borgmatic ls -la /mnt/borg-repository/
If source directories are on NFS or CIFS mounts, ensure the mount options allow root access (no_root_squash for NFS, or appropriate uid/gid mapping for CIFS). Alternatively, mount with specific uid/gid options in your Docker Compose file.
Config Validation Errors
Symptom: borgmatic: error: Problem reading configuration file or schema validation errors on startup.
Fix: Validate your configuration:
docker compose exec borgmatic borgmatic config validate
Common causes: YAML indentation errors, deprecated options from older borgmatic versions, or missing required fields. If upgrading from v1.x, the old before_backup/after_backup/on_error hooks still work but generate deprecation warnings — migrate them to the commands: list format. Check the configuration reference for your installed version.
Frequently Asked Questions
What is the difference between BorgBackup and Borgmatic?
BorgBackup is the backup engine — it handles deduplication, encryption, compression, and repository management. Borgmatic is a wrapper that adds YAML configuration, built-in scheduling via cron, notification hooks, database dump integration, and automated pruning. You can use BorgBackup directly with shell scripts, but borgmatic eliminates the need for custom scripting. See our BorgBackup vs Borgmatic comparison for details.
Can borgmatic back up Docker volumes from other containers?
Yes. Mount any host directory or Docker volume into the borgmatic container as a read-only source, then add the mount path to source_directories in your config. For example, mount -v nextcloud-data:/mnt/source/nextcloud:ro and add /mnt/source/nextcloud to the source list. For databases, use borgmatic’s built-in PostgreSQL/MySQL dump hooks instead of backing up raw database files — raw files are not crash-consistent.
How do I restore a single file from a borgmatic backup?
List available archives, then extract the specific file:
docker compose exec borgmatic borgmatic list
docker compose exec borgmatic borgmatic extract --archive latest --path /mnt/source/home/user/important-file.txt --destination /tmp/restore
The file is extracted to /tmp/restore inside the container. Copy it out with docker cp borgmatic:/tmp/restore/mnt/source/home/user/important-file.txt ./.
Is borgmatic v2 backward-compatible with v1 configurations?
Partially. Most v1 configuration keys work but generate deprecation warnings. The main breaking changes in v2: (1) before_backup/after_backup/on_error hooks are replaced by a commands: list format, (2) source directories must exist by default (source_directories_must_exist: true), (3) most Borg warnings are treated as errors. Run borgmatic config validate after upgrading to catch issues.
How much disk space does borgmatic actually use compared to full backups?
BorgBackup’s deduplication means the first backup is roughly the size of your source data (minus compression savings). Each subsequent backup stores only changed chunks — typically 1-5% of the total data size per daily backup for a typical server. A 100 GB dataset might use 110 GB for the first backup and grow by 1-5 GB per day, depending on change rate. After pruning, old archives release deduplicated chunks no longer referenced.
Can I run borgmatic on a schedule other than cron?
Yes. The Docker image uses cron internally, but you can override it. Remove the entry from crontab.txt and trigger borgmatic externally with a systemd timer or orchestration tool: docker compose exec borgmatic borgmatic --verbosity 1. This gives you more control over scheduling, dependencies, and logging compared to the container’s built-in cron.
What happens if a backup is interrupted by a server crash or network drop?
BorgBackup handles interrupted backups gracefully. The repository remains consistent — incomplete archives are discarded on the next run. You may need to break a stale lock (borg break-lock) if the process was killed without cleanup. No data is lost or corrupted. The next backup run deduplicates against existing data and completes normally.
Resource Requirements
- RAM: 256 MB idle (crond waiting). During backup: 512 MB–1 GB depending on dataset size and chunk cache. Large repositories (multi-TB) can use 2+ GB during integrity checks.
- CPU: Low during idle. Moderate during backup (compression and encryption). Single-threaded for most operations — borg does not parallelize within a single backup.
- Disk: The borgmatic container itself is small (~150 MB). Repository size depends on your data and retention policy. Deduplication typically achieves 5:1 to 20:1 compression ratios for incremental backups after the first full backup.
Verdict
Borgmatic is the best way to run BorgBackup in a Docker environment. If you already understand and trust borg’s deduplication and encryption (and you should — it is one of the most battle-tested backup engines available), borgmatic removes the operational burden of writing shell scripts, managing cron jobs, and remembering the right flags. The YAML configuration is clear, the built-in scheduling eliminates external cron dependencies, and the notification and database dump integrations make it genuinely hands-off.
Compared to Duplicati, borgmatic is more CLI-oriented and lacks a web UI, but it produces dramatically more efficient backup repositories and is far more reliable for large datasets. Duplicati’s web interface is friendly but its backup engine has a history of database corruption on long-running backup sets.
Compared to Restic, borgmatic offers more structured automation out of the box. Restic is a fantastic backup engine with broader native backend support (S3, B2, Azure, GCS without rclone), but you need to wrap it in your own scripts and scheduling. If you want a Docker container that handles everything — scheduling, pruning, verification, notifications — borgmatic is the more complete solution.
Use borgmatic if: You want automated, encrypted, deduplicated backups running in Docker with minimal ongoing maintenance. You value data integrity and efficient storage over a graphical interface.
Look elsewhere if: You need a web UI for backup management (Duplicati), native S3/B2 support without rclone (Restic), or you are backing up a single machine and prefer a simpler tool.
Related
- Borgmatic vs BorgBackup: What’s the Difference?
- Borgmatic vs Vorta: Which BorgBackup Frontend?
- Kopia vs Borgmatic: Backup Tools Compared
- Restic vs Borgmatic: Which Backup Tool Fits?
- How to Self-Host BorgBackup
- How to Self-Host Duplicati with Docker
- How to Self-Host Restic Backup
- BorgBackup vs Borgmatic
- Borgmatic vs Restic
- Borgmatic vs Kopia
- Duplicati vs Borgmatic
- Restic vs BorgBackup
- Best Self-Hosted Backup Solutions
- Backup Strategy: The 3-2-1 Rule
- Docker Compose Basics
- Reverse Proxy Setup
Get self-hosting tips in your inbox
Get the Docker Compose configs, hardware picks, and setup shortcuts we don't put in articles. Weekly. No spam.
Comments