You will lose data. Not if. When. Drive fails, power supply spikes, software bug corrupts a database, you delete the wrong Docker volume. Backups are cheap insurance.
This guide gets you a working backup system in 30 minutes. Not a perfect system. A working one. You can improve it later.
What to Back Up
Three categories:
- Config files - Docker compose files, Nginx configs, env files. Small, change infrequently, easy to back up.
- Application data - Database files, user uploads, state. This is what you’d miss most.
- System state - Package lists, cron jobs, custom scripts. Annoying to recreate.
For level 1, focus on config files and application data. System state comes later.
The 3-2-1 Rule
Three copies of your data, on two different types of media, with one copy offsite.
- Copy 1 - Your live data on the server (this is what you’re backing up)
- Copy 2 - Backup to a locally attached drive or networked NAS
- Copy 3 - Backup to remote storage (another server, cloud, friend’s house)
For this guide, you’ll set up copy 2 (local) and copy 3 (remote). Copy 1 is your running server.
Method 1: Restic
Restic is simple. One binary, no daemon, no config file. It backs up files, encrypts them, and stores them in a repository.
Install
sudo apt install restic
Initialize a Repository
You need somewhere to store backups. Two options:
Option A: Local drive
Plug in an external drive or USB stick:
sudo mkdir /mnt/backup
sudo mount /dev/sdX1 /mnt/backup
restic init --repo /mnt/backup/restic-repo
Option B: Remote storage
Use any server you have SSH access to, or a Backblaze B2 bucket:
restic init --repo sftp:user@backup-server:/backups/restic-repo
Both ask for a repository password. This encrypts your backups. Pick something you won’t forget. Write it down somewhere.
Back Up Docker Volumes
This is the most useful thing you can back up on a homelab:
# Stop services so databases don't get corrupted mid-backup
docker compose -f ~/docker-services/docker-compose.yml down
# Back up the entire directory
restic --repo /mnt/backup/restic-repo backup ~/docker-services
# Restart services
docker compose -f ~/docker-services/docker-compose.yml up -d
The down and up is important. A running Postgres or MySQL gives you a corrupted backup if it’s writing when you copy files.
Automate It
Create a script at /usr/local/bin/backup.sh:
#!/bin/bash
export RESTIC_REPOSITORY="/mnt/backup/restic-repo"
export RESTIC_PASSWORD="your-password"
# Docker services
docker compose -f /home/youruser/docker-services/docker-compose.yml down
restic backup /home/youruser/docker-services
docker compose -f /home/youruser/docker-services/docker-compose.yml up -d
# Important configs
restic backup /etc/nginx /etc/letsencrypt
# Clean up old backups (keep last 7 daily, 4 weekly, 12 monthly)
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
Make it executable and add a cron job:
chmod +x /usr/local/bin/backup.sh
sudo crontab -e
# Add this line to run at 3am daily:
0 3 * * * /usr/local/bin/backup.sh
Restore
When you need it:
restic -r /mnt/backup/restic-repo snapshots
# Pick a snapshot ID
restic -r /mnt/backup/restic-repo restore <snapshot-id> --target /tmp/restore
Everything goes to /tmp/restore. Copy what you need back into place.
Method 2: Borg
Borg is older than Restic and more efficient with storage. It deduplicates and compresses automatically. If you’re backing up Docker volumes that don’t change much day to day, Borg stores each new backup as just the differences.
Install
sudo apt install borgbackup
Initialize a Repository
borg init --encryption=repokey /mnt/backup/borg-repo
Back Up
# Same down/up pattern as Restic
docker compose -f ~/docker-services/docker-compose.yml down
borg create --stats --progress \
/mnt/backup/borg-repo::docker-compose-config \
~/docker-services
docker compose -f ~/docker-services/docker-compose.yml up -d
The :: part is the archive name. Use something descriptive with the date.
Automate
Same pattern as Restic. Script + cron.
#!/bin/bash
export BORG_REPO="/mnt/backup/borg-repo"
export BORG_PASSPHRASE="your-password"
docker compose -f /home/youruser/docker-services/docker-compose.yml down
borg create --stats ::{now:%Y-%m-%d} /home/youruser/docker-services
docker compose -f /home/youruser/docker-services/docker-compose.yml up -d
borg prune --keep-daily 7 --keep-weekly 4 --keep-monthly 12
Restore
borg list /mnt/backup/borg-repo
borg extract /mnt/backup/borg-repo::docker-compose-config
Restic vs Borg
Use Restic if you want cloud storage (Backblaze B2, S3, SFTP) and don’t want to think about it. Restic handles remote repos well.
Use Borg if you’re backing up to a local drive and want maximum storage efficiency. Borg’s deduplication is better than Restic’s.
Verify Your Backups
A backup you can’t restore is not a backup. Test this now:
# Pick any snapshot and restore to a temp directory
restic -r /mnt/backup/restic-repo restore latest --target /tmp/test-restore
ls -la /tmp/test-restore
If you can see your files, the backup works. Do this every few months. Automate it if you can.
Offsite Backup
The local backup covers drive failure. It doesn’t cover fire, flood, theft, or ransomware. You need one copy offsite.
Simple option: a second cron job that calls rclone to sync your local backup repo to Backblaze B2 or another cheap storage provider.
# Install rclone
sudo apt install rclone
# Configure (interactive, follow the prompts)
rclone config
# Sync backups offsite
rclone sync /mnt/backup b2:my-homelab-backups
This runs after your daily backup. If the house burns down, your data is in Backblaze.
Next Steps
You can back up and restore your data. That’s the hard part. Next: Linux Basics to get comfortable on the command line.