Restic + Backblaze B2 w homelabie — backup który realnie działa, kosztuje 5 zł/mc i daje spać
Po dwóch latach z różnymi rozwiązaniami (Borg, Duplicati, ręczne rsync) wylądowałem na Restic + B2. Pełen setup z systemd timer, encryption, retention policy i monitoringiem do Telegrama.

Pierwszy raz „prawdziwy" backup zrobiłem po stracie Proxmox VM z databasie betting-hope w 2024. SSD się rozsypał w środku tygodnia, ostatni snapshot miał 3 dni, czyli straciłem 3 dni danych. Od tamtej pory mam pewną zasadę: dane bez backupu to dane na czas określony.
Dziś u mnie Restic + Backblaze B2 obsługuje backupy 4 maszyn, 6 baz danych, ~80 GB danych. Koszt: 5 zł/mc. Wiarygodność: testowane restore co miesiąc, działa.
To jest dokładny setup, copy-paste, nie filozofia.
Dlaczego Restic, a nie Borg / Duplicati / Restic Browser
Krótko:
- Borg, świetny, ale chunked storage trzyma dane w binary format trudnym do recovery jak utracisz binarkę. Restic ma open spec.
- Duplicati, UI ładny, ale crashe przy dużych repo, słaby support polish backupów.
- Restic, Go single binary, encryption AES-256 + Poly1305, dedup, snapshots, wsparcie 15+ backendów (B2, S3, Azure, GCS, SFTP, REST itp.). Stable od 2017.
- Co odpada od razu: gołe rsync (brak dedup, brak encryption), tar.gz (brak incremental).
Architektura — co backup, gdzie, jak często
┌─────────────────────────────────────────┐
│ Główna maszyna (homelab core) │
│ - /home/kkaletka (configs, projekty) │
│ - /var/lib/docker/volumes (DB volumes) │
│ - /etc (system config) │
│ - PostgreSQL dump (każde 6h) │
└─────────────────────────────────────────┘
│
│ restic backup --tag $(hostname)
▼
┌─────────────────────────────────────────┐
│ Backblaze B2 bucket: kkaletka-backup │
│ region: eu-central │
│ encryption: server-side + client-side │
│ retention: 7d daily + 4w weekly + 12m │
└─────────────────────────────────────────┘Cztery maszyny push'ują do tego samego bucketu, każda ma własny tag. Restore selektywny per host.
Setup, krok po kroku
1. Instalacja Restic (Ubuntu/Debian)
# Snap psuje dependencies, używaj github releases
wget https://github.com/restic/restic/releases/download/v0.17.3/restic_0.17.3_linux_amd64.bz2
bunzip2 restic_*.bz2
sudo install -m 755 restic_* /usr/local/bin/restic
restic version # 0.17.3 compiled with go1.232. Konto Backblaze + bucket
- Załóż konto na backblaze.com (15 GB free, dalej 0.005 USD/GB/mc)
- New Bucket:
kkaletka-backup, private, encryption: server-side, region: eu-central - Application Key z scope tylko do tego bucketu (nie używaj master key)
3. Inicjalizacja repo
# Plik z env vars (nie commituj do repo!)
cat > /etc/restic/env <<EOF
B2_ACCOUNT_ID=your-key-id
B2_ACCOUNT_KEY=your-app-key
RESTIC_REPOSITORY=b2:kkaletka-backup:/
RESTIC_PASSWORD=$(openssl rand -base64 32)
EOF
chmod 600 /etc/restic/env
# Init (uruchom RAZ na całe życie repo!)
source /etc/restic/env
restic initKrytyczne: zapisz RESTIC_PASSWORD w password managerze. Bez niego, repo bezużyteczne. Stracisz hasło → stracisz wszystko.
4. Skrypt backup
#!/usr/bin/env bash
# /usr/local/bin/backup-restic.sh
set -euo pipefail
source /etc/restic/env
HOST=$(hostname)
LOG=/var/log/restic-${HOST}.log
# 1. Postgres dump
pg_dump -U postgres -d betting_hope | \
restic backup --stdin --stdin-filename "${HOST}-postgres-$(date +%F).sql" \
--tag postgres --tag $HOST 2>&1 | tee -a $LOG
# 2. File system
restic backup \
/home/kkaletka \
/etc \
/var/lib/docker/volumes \
--exclude-file=/etc/restic/excludes \
--tag files --tag $HOST 2>&1 | tee -a $LOG
# 3. Apply retention policy
restic forget --prune \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 12 2>&1 | tee -a $LOG
# 4. Integrity check (subset, full check 1x/mc)
restic check --read-data-subset=5% 2>&1 | tee -a $LOG5. Plik exclude
# /etc/restic/excludes
node_modules
.next
__pycache__
*.pyc
*.tmp
*.log
**/cache/**
.cache
.git/objects/pack
target/debug
target/releaseWycina ~40% objętości. node_modules zwłaszcza, restoreujesz npm install, nie 800 MB JS bibliotek.
6. Systemd timer (zamiast cron)
# /etc/systemd/system/restic-backup.service
[Unit]
Description=Restic backup
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-restic.sh
EnvironmentFile=/etc/restic/env
Nice=10
IOSchedulingClass=idle
[Install]
WantedBy=multi-user.target# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Daily restic backup at 03:30
[Timer]
OnCalendar=*-*-* 03:30:00
Persistent=true
RandomizedDelaySec=15min
[Install]
WantedBy=timers.targetsudo systemctl enable --now restic-backup.timer
systemctl list-timers restic-backup.timer # weryfikacjaNice=10 + IOSchedulingClass=idle, backup nie zarzyna systemu. RandomizedDelaySec, nie wszystkie maszyny startują dokładnie 3:30.
Monitoring do Telegrama
Backup który nie woła gdy padnie = brak backupu.
# Dorzuć do /usr/local/bin/backup-restic.sh
TELEGRAM_TOKEN="your-bot-token"
CHAT_ID="your-chat-id"
notify_failure() {
local msg="$1"
curl -fsS -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" \
--data-urlencode "text=⚠️ Backup ${HOST} failed: ${msg}"
}
trap 'notify_failure "Skrypt padł na linii $LINENO"' ERRset -e + trap, każde niezerowe exit z wnętrza skryptu pójdzie w Telegrama z numerem linii. Po roku używania mam ~3 alerty/rok (głównie B2 maintenance windows).
Restore — najważniejszy test
Backup nieprzetestowany = brak backupu (część 2).
# Listuj snapshots
restic snapshots --tag postgres
# Restore konkretny plik z konkretnego snapshota
restic restore latest --target /tmp/restore --include /home/kkaletka/.env
# Restore postgresa
restic dump latest /openclaw-master-postgres-2026-05-11.sql | \
psql -U postgres -d betting_hope_restoredRobię test restore raz na miesiąc, do osobnej VM, sprawdzam że databasy działają. Zajmuje 15 minut, daje 100% pewność.
Realne koszty
B2 storage: 80 GB × 0.005 USD/GB/mc = 0.40 USD/mc
B2 download (test): 5 GB × 0.01 USD = 0.05 USD/mc
B2 API calls: ~0.02 USD/mc
─────────────────────────
~0.47 USD/mc = ~2 zł/mcPlus opcjonalny second backup do Hetzner Storage Box (1 TB za 12 zł/mc), przyda się jeśli B2 padnie globalnie. U mnie to:
# Dodatkowy snapshot weekly do Hetzner
restic -r sftp:[email protected]:/restic snapshots3-2-1 zasada: 3 kopie, 2 różne media, 1 off-site. Production data → main disk + B2 + Hetzner.
Co bym zrobił inaczej
- Wcześniej zaczął test restore. Pierwszy restore robiłem po 4 miesiącach od setupu, okazało się że
RESTIC_PASSWORDsię gubił przy kolejnych runach. Test od początku by to wyłapał. - Osobny bucket per maszyna. Trzymam wszystko w jednym, ale to znaczy że każda maszyna potencjalnie może czytać backupy innej. Z osobnymi bucketami i app keys per-host, better isolation.
- Healthchecks.io zamiast tylko Telegram. Dla cron-style jobów healthchecks.io daje "ping that didn't come", wykryje jak skrypt się w ogóle nie odpalił (a nie tylko padł).
TL;DR — checklist
- Restic + Backblaze B2 (5 zł/mc dla 80 GB)
-
RESTIC_PASSWORDw password managerze, nie tylko na dysku - Exclude
node_modules,__pycache__,target/, oszczędza 40% - Retention
--keep-daily 7 --keep-weekly 4 --keep-monthly 12 - Systemd timer (
Nice=10,IOSchedulingClass=idle,RandomizedDelaySec=15min) - Telegram/Healthchecks.io notify on failure
- Test restore raz na miesiąc, do osobnej VM
- Bonus: drugi off-site backup (Hetzner Storage Box)
Setup zajmuje 2-3h, kosztuje grosze, i daje spać. To ostatnia rzecz która ma być budgetem.