Développement Web

Backups automatiques Immich avec Restic vers MinIO et Backblaze B2 (2026)

11 دقائق للقراءة

📍 Article principal de la série : Immich 2026 : guide pratique.

Une bibliothèque de 30 000 photos familiales sans sauvegarde testée est une bombe à retardement. Ce tutoriel détaille la procédure complète pour sauvegarder Immich (base Postgres + fichiers physiques + métadonnées) selon la règle 3-2-1, avec restic pour la déduplication et le chiffrement, vers Backblaze B2 (offsite) et MinIO (local). Méthode validée chez plusieurs photographes professionnels et familles à Casablanca, Dakar, et Tunis.

Prérequis

  • Immich en production avec données réelles.
  • Compte Backblaze B2 + bucket privé.
  • Optionnel : MinIO sur second VPS pour double sauvegarde.
  • Niveau attendu : intermédiaire (cron, bash, restic).
  • Temps estimé : 1 heure setup + 30 minutes test mensuel.

Étape 1 — Stratégie 3-2-1 appliquée à Immich

3 copies : production (Hetzner Storage Box) + sauvegarde MinIO local + sauvegarde Backblaze B2 offsite. 2 supports : NFS Storage Box + S3 distant. 1 hors-site : Backblaze EU Central. Cette règle protège contre : panne disque (rare avec Hetzner), suppression accidentelle, ransomware, désastre datacenter, erreur humaine admin.

Étape 2 — Identifier les éléments à sauvegarder

  • Base Postgres (métadonnées, albums, utilisateurs, mots de passe hashés).
  • Fichiers physiques sur /mnt/photos/immich-upload.
  • Configuration : .env de Coolify, certificats Let’s Encrypt.
  • Modèles ML personnalisés (si vous avez entraîné des reconnaissances faciales spécifiques).

Étape 3 — Installer restic et rclone

apt install -y restic rclone
restic version  # 0.17.x ou supérieur
rclone version  # 1.66 ou supérieur

Étape 4 — Configurer le repository restic Backblaze

export RESTIC_REPOSITORY="s3:s3.eu-central-003.backblazeb2.com/immich-backups-prod"
export AWS_ACCESS_KEY_ID="votre-keyID-B2"
export AWS_SECRET_ACCESS_KEY="votre-applicationKey-B2"
export RESTIC_PASSWORD="passphrase-très-très-forte-pour-restic"
restic init

Stocker la RESTIC_PASSWORD dans Vaultwarden + sur papier dans un coffre + sur YubiKey. Trois copies physiques minimum.

Étape 5 — Script de sauvegarde Postgres

Fichier /srv/scripts/immich-backup-db.sh :

#!/bin/bash
set -euo pipefail
TS=\$(date +%Y%m%d-%H%M%S)
TMP=/tmp/immich-pg-\$TS

# Dump Postgres consistent
docker exec immich-postgres pg_dump -U immich -d immich -F c -f /tmp/immich-\$TS.dump
docker cp immich-postgres:/tmp/immich-\$TS.dump \$TMP.dump
docker exec immich-postgres rm /tmp/immich-\$TS.dump

# Restic
export RESTIC_REPOSITORY="s3:s3.eu-central-003.backblazeb2.com/immich-backups-prod"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
export AWS_ACCESS_KEY_ID="votre-keyID"
export AWS_SECRET_ACCESS_KEY="votre-applicationKey"

restic backup \$TMP.dump --tag immich-db --tag prod
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
rm -f \$TMP.dump

# Notification
curl -s "https://ntfy.sh/votre-topic" -d "Immich DB backup OK \$TS"

Étape 6 — Script de sauvegarde fichiers

Fichier /srv/scripts/immich-backup-files.sh :

#!/bin/bash
set -euo pipefail

export RESTIC_REPOSITORY="s3:s3.eu-central-003.backblazeb2.com/immich-backups-prod"
export RESTIC_PASSWORD_FILE="/root/.restic-password"
export AWS_ACCESS_KEY_ID="votre-keyID"
export AWS_SECRET_ACCESS_KEY="votre-applicationKey"

# Backup incrémental fichiers
restic backup /mnt/photos/immich-upload \
  --tag immich-files --tag prod \
  --exclude "*/encoded-video/*" \
  --exclude "*/thumbs/*"

# Forget retention
restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 24 --prune

curl -s "https://ntfy.sh/votre-topic" -d "Immich files backup OK"

Note : on exclut les vignettes et vidéos transcodées (regénérables depuis l’original). Économie 30-40% du volume.

Étape 7 — Planifier les sauvegardes

chmod +x /srv/scripts/immich-backup-*.sh

crontab -e
# DB toutes les 6h
0 */6 * * * /srv/scripts/immich-backup-db.sh >> /var/log/immich-backup.log 2>&1
# Fichiers toutes les 12h
0 4,16 * * * /srv/scripts/immich-backup-files.sh >> /var/log/immich-backup.log 2>&1
# Vérification hebdomadaire intégrité
0 5 * * 0 restic check --read-data-subset=10% >> /var/log/immich-backup.log 2>&1

Étape 8 — Test de restauration mensuel

Le test n’est pas optionnel. Procédure mensuelle sur VPS staging :

# Liste snapshots
restic snapshots --tag immich-db | tail -5

# Restaurer le dernier
restic restore latest --tag immich-db --target /tmp/restore

# Vérifier le dump
ls -la /tmp/restore/tmp/immich-*.dump

# Restaurer dans un Postgres test
docker run -d --name pg-test postgres:16
sleep 10
docker cp /tmp/restore/tmp/immich-*.dump pg-test:/tmp/
docker exec pg-test pg_restore -U postgres -d postgres /tmp/immich-*.dump

# Compter les assets
docker exec pg-test psql -U postgres -c "SELECT COUNT(*) FROM assets;"
# Doit correspondre à la prod ± quelques heures

Étape 9 — Documentation et runbook

Créer un wiki interne avec : URLs des buckets, IDs des keys (chiffrés), passphrases (référence Vaultwarden), date du dernier test, procédure d’urgence si Immich est down. Sans runbook, en cas de panique, vous oubliez les étapes.

Erreurs fréquentes

Erreur Cause Solution
Restic password perdu Stockage unique 3 copies indépendantes obligatoire
Backup interrompu Disque plein temporaire Vérifier df avant chaque run
Latence upload élevée Bande passante saturée jour Planifier 2h-5h heure locale
Test restauration jamais fait Procrastination Cron mensuel avec alerte si pas exécuté
Bucket B2 saturé Forget non lancé Vérifier restic forget --prune
Doublons gigaoctets Sauvegardes hors restic Tout consolider dans le même repo restic

Ce qu’il faut savoir avant de déployer en sous-région

Trois précisions. Coût Backblaze B2 : pour 200 Go (15 000-30 000 photos), compter 1,20 USD/mois. Pour 1 To, 6 USD/mois. Marginal face à Google One. Egress gratuit Cloudflare : Backblaze + Cloudflare Bandwidth Alliance permet de restaurer sans frais, important si une famille au Mali doit récupérer 50 Go en urgence après perte de serveur. Test sur infrastructure locale : effectuer le test mensuel de restauration sur un Raspberry Pi 5 chez l’admin permet de valider que la procédure marche même hors infrastructure principale.

Tutoriels frères

FAQ

Pourquoi Backblaze plutôt qu’AWS S3 ? Coût (6 USD/To vs 23 USD), egress gratuit via Cloudflare, simplicité. AWS S3 reste meilleur pour intégrations enterprise.

Restic vs Borgbackup vs Duplicati ? Restic gagne sur S3 native + déduplication + multi-OS. Borg meilleur sur SSH local, Duplicati GUI mais moins fiable.

Que faire si le serveur Immich crash totalement ? Réinstaller Immich propre, restore Postgres + restore fichiers depuis restic. Comptez 4-8 heures pour 100 Go. Documenter la procédure.

Comment vérifier l’intégrité régulièrement ? restic check --read-data-subset=10% hebdomadaire, full restic check --read-data trimestriel.

Backups chiffrés résistent-ils au quantum computing ? Restic utilise AES-256, considéré quantum-resistant pour la décennie 2026-2035 selon NIST.

Dans la continuité

Etape 1 : verifier les prerequis serveur

Immich stocke deux choses : les fichiers media (photos, videos) sur disque, et les metadonnees dans PostgreSQL. Une vraie sauvegarde doit couvrir les deux. Avant de toucher a Restic, on s’assure que le serveur dispose des outils minimum. Sur un VPS Hetzner ou Contabo utilise par les studios photo de Dakar, le check prend 2 minutes.

# Verifier les versions disponibles
docker --version          # 24+ recommande
docker compose version    # plugin v2 obligatoire
restic version            # 0.17+ pour le sftp moderne
mc --version              # client MinIO si MinIO local

Si restic n’est pas installe, on l’ajoute via apt sur Debian 12 ou Ubuntu 24.04. Une version a jour evite les bugs de chiffrement des anciennes branches 0.14.

Etape 2 : structurer les volumes a sauvegarder

Immich cree par defaut un repertoire UPLOAD_LOCATION qui contient toutes les photos. Le compose Postgres tourne avec son propre volume nomme. On identifie precisement les chemins avant de configurer Restic.

# Inspection des volumes Immich
docker compose -f /opt/immich/docker-compose.yml config | grep -A2 volumes
# Sortie typique :
# UPLOAD_LOCATION=/opt/immich/library
# pgdata:/var/lib/postgresql/data

Notez ces chemins. Ils sont les deux cibles a sauvegarder. Les autres volumes (model-cache, redis) sont reconstruits automatiquement, on les ignore.

Etape 3 : preparer un dump Postgres coherent

Sauvegarder un fichier Postgres « a chaud » donne un backup corrompu une fois sur deux. La methode propre est pg_dumpall depuis l’interieur du container, vers un fichier que Restic snapshote ensuite.

#!/bin/bash
# /opt/immich/scripts/dump-pg.sh
set -e
DUMP_DIR=/opt/immich/dumps
mkdir -p "$DUMP_DIR"
docker compose -f /opt/immich/docker-compose.yml \
  exec -T database pg_dumpall -U postgres \
  | gzip > "$DUMP_DIR/immich-$(date +%F).sql.gz"
# garder 7 jours locaux
find "$DUMP_DIR" -name "*.sql.gz" -mtime +7 -delete

Apres execution, un fichier immich-2026-05-05.sql.gz apparait dans /opt/immich/dumps. Sa taille typique est 5 a 50 Mo selon le volume de metadonnees. Si le fichier fait moins de 1 Ko, le dump a echoue.

Etape 4 : initialiser le depot Restic sur MinIO local

MinIO sert de premier etage : depot rapide sur le LAN, restauration en minutes. On cree un bucket dedie, puis on initialise le depot Restic chiffre. Le mot de passe Restic est critique : sans lui, plus aucune restauration possible.

# Creer le bucket via mc
mc alias set local http://192.168.1.10:9000 ADMIN SECRET
mc mb local/immich-backup

# Variables d'environnement Restic
export RESTIC_REPOSITORY="s3:http://192.168.1.10:9000/immich-backup"
export RESTIC_PASSWORD_FILE=/root/.restic-pass
export AWS_ACCESS_KEY_ID=ADMIN
export AWS_SECRET_ACCESS_KEY=SECRET

# Initialisation
restic init

La commande affiche « created restic repository ». Si vous voyez une erreur « config already initialized », c’est que le bucket contient deja un depot : reutilisez-le ou changez de bucket.

Etape 5 : configurer le second etage Backblaze B2

MinIO local protege contre la perte d’un disque. Backblaze B2 protege contre l’incendie du local serveur. Pour un studio photo a Abidjan, le tarif Backblaze est imbattable : environ 6 USD par To et par mois en mai 2026 (verifiez sur backblaze.com car les prix bougent), facture en USD donc convertir en FCFA selon le taux du jour.

# Initialiser un second depot sur B2
export B2_ACCOUNT_ID=KEY_ID
export B2_ACCOUNT_KEY=APP_KEY
restic -r b2:immich-offsite:/ init

# Repeter les sauvegardes vers les deux depots
restic -r s3:http://192.168.1.10:9000/immich-backup backup ...
restic -r b2:immich-offsite:/ backup ...

Le double depot est la regle 3-2-1 appliquee : 3 copies des donnees (production + local + offsite), sur 2 supports differents (disque local + cloud), dont 1 hors-site (B2 dans un autre continent).

Etape 6 : script de backup orchestre

On regroupe tout dans un script unique declenche par cron. Le script enchaine dump Postgres, backup vers MinIO, backup vers B2, et purge des anciennes versions selon une politique de retention.

#!/bin/bash
# /opt/immich/scripts/backup-all.sh
set -euo pipefail
source /root/.restic-env

# 1. Dump Postgres
/opt/immich/scripts/dump-pg.sh

# 2. Backup local MinIO
restic -r "$REPO_LOCAL" backup \
  /opt/immich/library /opt/immich/dumps \
  --tag daily

# 3. Backup offsite B2
restic -r "$REPO_OFFSITE" backup \
  /opt/immich/library /opt/immich/dumps \
  --tag daily

# 4. Purge selon retention
for REPO in "$REPO_LOCAL" "$REPO_OFFSITE"; do
  restic -r "$REPO" forget --prune \
    --keep-daily 7 --keep-weekly 4 --keep-monthly 6
done

Chaque execution journaliere ajoute un snapshot et purge les plus anciens selon la politique. Vous gardez 7 jours, 4 semaines et 6 mois en rolling, ce qui couvre les besoins d’un studio sans exploser le stockage.

Etape 7 : planifier avec cron

On planifie en heures creuses, typiquement 2h du matin heure de Dakar (UTC), quand l’upload Immich est nul. On loggue tout dans /var/log/immich-backup.log pour faciliter le debug.

# crontab -e (en root)
0 2 * * * /opt/immich/scripts/backup-all.sh >> /var/log/immich-backup.log 2>&1

# Verifier le lendemain
tail -50 /var/log/immich-backup.log
restic -r "$REPO_LOCAL" snapshots | tail -5

Au lendemain matin, vous devez voir un nouveau snapshot date du jour. Si rien n’apparait, le log indique la cause (espace disque, credential MinIO expire, etc.).

Etape 8 : tester la restauration tous les mois

Un backup non teste n’est pas un backup. Une fois par mois, on monte un snapshot dans un repertoire temporaire et on verifie qu’on peut lire un fichier media et restaurer le dump Postgres dans un container test. C’est la seule garantie reelle.

# Lister les snapshots
restic -r "$REPO_LOCAL" snapshots

# Monter un snapshot en lecture seule
mkdir -p /mnt/restore
restic -r "$REPO_LOCAL" mount /mnt/restore &
ls /mnt/restore/snapshots/latest/opt/immich/library/

# Restaurer un dump dans un Postgres test
zcat /mnt/restore/snapshots/latest/opt/immich/dumps/*.sql.gz \
  | docker exec -i pg-test psql -U postgres

Si vous voyez vos vignettes et que la base test contient les tables Immich, votre chaine de backup est validee. Notez la date du dernier test reussi quelque part de visible.

FAQ

Pourquoi Restic plutot que Borg ?

Restic gere nativement S3, B2, Azure et GCS. Borg necessite un depot SSH ou rclone. Pour un setup multi-cloud, Restic est plus simple. Borg reste plus rapide en deduplication pure si tout est local.

Quelle taille de bucket prevoir ?

Comptez 1.3x la taille de votre library Immich, grace a la deduplication et compression Restic. 500 Go de photos = environ 650 Go de depot. Lisez aussi notre guide pour migrer de Google Photos vers Immich.

Bonus : alertes en cas d’echec backup

Un cron silencieux qui echoue trois nuits de suite, c’est trois jours de donnees a risque. On branche une notification simple via healthchecks.io ou un webhook Discord pour etre alerte au moindre probleme. La configuration prend 5 minutes, le retour sur investissement se compte en sommeil retrouve pour l’administrateur systeme.

# Creer un check sur healthchecks.io et adapter le script
URL_PING="https://hc-ping.com/UUID-VOTRE-CHECK"

# En tete du script
curl -fsS -m 10 --retry 5 -o /dev/null "$URL_PING/start"

# A la fin si succes
curl -fsS -m 10 --retry 5 -o /dev/null "$URL_PING"

# En cas d'erreur (trap)
trap 'curl -fsS -m 10 -o /dev/null "$URL_PING/fail"' ERR

Si le ping de fin n’arrive pas dans la fenetre attendue (ex : 25 heures), healthchecks vous envoie un email et un push Slack. Vous detectez l’incident le matin meme, pas trois semaines plus tard quand un client demande une restauration.

Cette discipline operationnelle distingue un studio amateur d’un studio professionnel : la sauvegarde n’est jamais terminee, elle vit chaque jour avec la production photo.

مشاركة