Un volume Docker persistant ou une archive conservée sur le même VPS ne suffit pas. Une vraie sauvegarde doit rester disponible après la perte du serveur principal et sa restauration doit être vérifiée.
L’architecture retenue
Le serveur produit d’abord une copie cohérente de la base, crée une archive contrôlée par SHA-256, puis l’envoie dans un dépôt Restic chiffré sur un NAS distant. Un heartbeat confirme le succès uniquement après la copie distante.
1. Identifier les données à protéger
Listez les bases de données, fichiers envoyés par les utilisateurs, configurations non reproductibles et secrets conservés hors du dépôt. Les images de conteneurs et le code Git sont généralement reconstructibles ; les données applicatives ne le sont pas.
2. Créer une copie SQLite cohérente
Copier directement une base SQLite active peut ignorer les écritures présentes dans le journal WAL. Utilisez l’API de sauvegarde SQLite, puis contrôlez la source et la copie.
python3 - <<'PY'
import sqlite3
source = sqlite3.connect("file:/data/database.sqlite?mode=ro", uri=True)
target = sqlite3.connect("/tmp/database.sqlite")
source.backup(target)
assert target.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
target.close()
source.close()
PY
3. Archiver et vérifier localement
Créez une archive datée et sa somme SHA-256. Cette copie locale facilite une récupération rapide, mais elle reste insuffisante seule.
tar -czf "/var/backups/site/site-$STAMP.tgz" -C /tmp database.sqlite
cd /var/backups/site
sha256sum "site-$STAMP.tgz" > "site-$STAMP.tgz.sha256"
sha256sum -c "site-$STAMP.tgz.sha256"
4. Relier le serveur au NAS sans exposer SSH
Un réseau privé maillé comme Tailscale permet au VPS de joindre le NAS sans redirection publique du port 22. Créez sur le NAS un compte SFTP dédié, limité au dossier de sauvegarde, et utilisez une clé SSH distincte.
5. Initialiser le dépôt Restic chiffré
Conservez le mot de passe Restic et la clé privée hors du dépôt Git, dans des fichiers accessibles uniquement à root. Gardez aussi une copie du mot de passe en dehors du serveur sauvegardé.
restic --repo 'sftp:backup@nas:Backups/site-restic' \
--password-file /etc/mon-site/restic-password init
restic --repo 'sftp:backup@nas:Backups/site-restic' \
--password-file /etc/mon-site/restic-password \
backup /var/backups/site --tag site-web
6. Appliquer une rétention
Une rétention quotidienne limite l’espace utilisé tout en conservant un historique utile. Adaptez-la à votre activité et à vos obligations.
restic forget --tag site-web --keep-daily 30 --prune
7. Envoyer le heartbeat après le succès
Placez l’appel de surveillance en toute fin du script. Si la création de l’archive, la copie Restic ou la rétention échoue, aucun signal ne doit être envoyé.
8. Lire régulièrement toutes les données
Planifiez un contrôle intégral hebdomadaire. Il vérifie les index, les snapshots et le contenu des packs, pas seulement leur présence.
restic check --read-data
9. Tester une vraie restauration
Un snapshot visible prouve que Restic a enregistré des données. Il ne prouve pas encore que l’archive applicative s’ouvre ni que la base qu’elle contient est cohérente. Le test utile restaure donc le dernier snapshot dans un répertoire isolé, vérifie chaque archive puis contrôle chaque base SQLite extraite.
Chargez d’abord les variables de votre dépôt Restic dans un terminal Bash, sans afficher le mot de passe. Le bloc suivant ne touche pas aux données actives et supprime automatiquement sa copie temporaire :
#!/usr/bin/env bash
set -eu
: "${RESTIC_REPOSITORY:?Variable absente}"
: "${RESTIC_PASSWORD_FILE:?Variable absente}"
RESTORE_DIR=$(mktemp -d /tmp/restic-restore-test.XXXXXX)
trap 'rm -rf -- "$RESTORE_DIR"' EXIT
restic \
--repo "$RESTIC_REPOSITORY" \
--password-file "$RESTIC_PASSWORD_FILE" \
restore latest \
--target "$RESTORE_DIR"
find "$RESTORE_DIR" -type f -name '*.tgz' -print -quit | grep -q . || {
echo 'Aucune archive .tgz restaurée' >&2
exit 1
}
while IFS= read -r -d '' archive
do
tar -tzf "$archive" >/dev/null
extract=$(mktemp -d "$RESTORE_DIR/extracted.XXXXXX")
tar -xzf "$archive" -C "$extract"
python3 - "$extract" <<'PY'
import sqlite3
import sys
from pathlib import Path
databases = [
path for path in Path(sys.argv[1]).rglob("*")
if path.is_file() and path.suffix in {".db", ".sqlite", ".sqlite3"}
]
if not databases:
raise SystemExit("Aucune base SQLite restaurée")
for path in databases:
connection = sqlite3.connect(f"file:{path}?mode=ro", uri=True)
try:
result = connection.execute("PRAGMA integrity_check").fetchone()[0]
finally:
connection.close()
print(f"{path.name}: {result}")
if result != "ok":
raise SystemExit(1)
PY
done < <(find "$RESTORE_DIR" -type f -name '*.tgz' -print0)
echo 'Restauration Restic et intégrité SQLite : OK'
Adaptez le filtre d’archive si votre sauvegarde n’utilise pas .tgz. Ne restaurez jamais directement par-dessus la base de production pour effectuer ce contrôle.
Les erreurs fréquentes
- considérer un volume Docker comme une sauvegarde ;
- archiver une base SQLite active sans prendre en compte WAL ;
- conserver toutes les copies sur le même VPS ;
- utiliser le compte administrateur du NAS ;
- envoyer le heartbeat avant la copie distante ;
- ne jamais tester la restauration.
Surveiller vos sauvegardes
Un heartbeat permet de détecter l’absence du signal attendu après une sauvegarde planifiée. Il complète la copie et les tests de restauration ; il ne les remplace pas.
Créer un contrôle de sauvegardeUne stratégie simple peut déjà être solide : une copie cohérente, une archive locale, un dépôt chiffré sur une autre machine, une surveillance et un test de restauration. Le résultat attendu n’est pas un fichier créé, mais une récupération prouvée.
Publié le 5 août 2026 · mis à jour le 23 août 2026.