jguillaumesio
prod-opsdevopsdocker

Des backups de base qui restaurent vraiment : le pgdata que j'ai perdu

J'ai supprimé un répertoire de données Postgres qui vivait dans un bind mount, sans rien derrière. Mon plan de backup, c'était l'espoir. Voici le setup de backup pas cher sur un seul VPS que j'aurais dû avoir, y compris l'étape que tout le monde saute.

J’ai supprimé le répertoire de données Postgres. Il vivait dans un bind mount sur l’hôte, il n’y avait pas de volume nommé pour le protéger, et il n’y avait aucun backup ailleurs. La donnée avait simplement disparu. Mon plan de backup, si je suis honnête, c’était l’espoir.

Voici la partie 9 d’une série sur le durcissement d’un SaaS construit en solo en production. Le setup et la liste complète des points faibles sont dans l’article pilier. Celui-ci est l’incident qui m’a appris qu’un backup qu’on n’a jamais restauré n’est pas un backup. C’est une hypothèse.

Comment la donnée était stockée, et pourquoi c’était fragile

Les données persistantes étaient dans des bind mounts sur l’hôte :

db:
  volumes:
    - ./postgres/data:/var/lib/postgresql/data
storage:
  volumes:
    - ./minio/data:/data

Les bind mounts sont acceptables en soi. La fragilité, c’était tout ce qu’il y avait autour : pas de volume nommé, pas de snapshot, et rien de copié hors de la machine. Donc la donnée avait exactement une copie, sur un disque, sur un VPS. La moindre erreur (un rm malheureux, un mauvais docker compose down -v, un docker system prune --volumes, contre lequel j’ai mis en garde dans l’article sur le disque plein) signifiait une perte définitive. Ce n’est pas encore un problème de backup, c’est un problème de “une seule copie de tout”, et c’est la condition préalable à la panne.

À quoi ressemble un vrai backup sur un seul VPS

Vous n’avez pas besoin de RDS managé ni d’un produit de backup. Sur un seul VPS avec un petit budget, trois couches vous couvrent.

1. Backups logiques avec pg_dump

Le backup le plus simple et le plus portable, c’est un dump logique. Il produit un fichier que vous pouvez restaurer dans n’importe quel Postgres, et il est parfait pour les bases petites à moyennes :

# nocturne, compressé, format custom (restaurable avec pg_restore)
pg_dump -Fc -d "$DATABASE_URL" -f "/backups/db-$(date +%F).dump"

Planifiez-le (cron sur l’hôte, ou un petit conteneur sidecar), gardez quelques jours en local, et passez à la partie qui vous protège réellement.

2. Envoyez-le hors de la machine avec restic

Un backup qui vit sur le même disque que la base meurt avec ce disque. Le backup doit quitter la machine. restic est idéal ici : c’est un binaire unique, il chiffre et déduplique, et il parle à du stockage objet pas cher (S3, Backblaze B2, n’importe quel bucket compatible S3) :

restic backup /backups /var/lib/minio/data     # dumps + stockage objet
restic forget --keep-daily 7 --keep-weekly 4 --prune

La ligne forget --prune n’est pas optionnelle. Elle applique une fenêtre de rétention bornée, ce qui garde le coût bas et, comme l’explique le guide de rétention RGPD, c’est aussi ce qui permet aux données d’un utilisateur supprimé de réellement disparaître de vos backups au lieu d’y vivre pour toujours.

Ça couvre la classique règle 3-2-1 : au moins trois copies, sur deux types de support, avec une hors-site. Le dépôt restic dans le stockage objet externe est votre copie hors-site.

3. La restauration à un instant donné, quand un jour de perte est de trop

pg_dump vous donne l’état de la nuit dernière. Si perdre une journée de données est inacceptable, montez aux backups physiques : pg_basebackup plus l’archivage continu des WAL vous donne la restauration à un instant donné (PITR), la capacité de restaurer à n’importe quel moment, pas seulement au dernier snapshot. C’est plus de pièces mobiles, donc ne l’ajoutez que quand votre objectif de point de reprise l’exige. Pour beaucoup de petits produits, un dump nocturne envoyé hors-site suffit vraiment.

L’étape que tout le monde saute : tester la restauration

Voici la partie qui transforme un backup plein d’espoir en un vrai. Un backup que vous n’avez jamais restauré est une hypothèse. Les backups échouent en silence de cent façons : un dump qui a planté à mi-chemin, un cron arrêté depuis des mois, une clé de chiffrement que personne n’a sauvegardée, un fichier présent mais corrompu. Vous découvrez lequel s’applique au pire moment possible, sauf si vous testez.

Alors automatisez une restauration. Régulièrement, tirez le dernier backup, restaurez-le dans un conteneur jetable, et lancez un contrôle de cohérence :

# restaurer le dump le plus récent dans un Postgres jetable et vérifier qu'il est réel
pg_restore -d "$SCRATCH_DB" "$(ls -t /backups/*.dump | head -1)"
psql -d "$SCRATCH_DB" -c "select count(*) from users;"   # un nombre sensé ?

Si ça passe au vert sur un planning, vous avez un backup. Si vous ne l’avez jamais lancé, vous avez un dossier de fichiers dont vous espérez que ce sont des backups. La différence ne devient visible que pendant un désastre, le seul moment où vous ne pouvez pas vous permettre de la découvrir.

Boucler la boucle : volumes nommés et garde-fou de suppression

Les backups rendent le stockage sous-jacent non fatal, mais corrigez aussi le stockage. Préférez les volumes nommés aux bind mounts ad-hoc : ils sont gérés par Docker, plus durs à rm par accident, et clairement séparés du répertoire de votre projet. Et traitez les commandes destructrices comme des armes chargées : ne lancez jamais docker compose down -v ni docker system prune --volumes sur une machine que vous n’avez pas auditée, parce que les deux supprimeront joyeusement la donnée dont vous dépendez.

La réponse en couches, c’est : des volumes nommés pour que les erreurs banales n’effacent pas la donnée, des backups restic hors-site pour survivre à la perte d’un disque ou d’une machine, et une restauration testée pour savoir réellement que les backups fonctionnent.

À retenir

J’ai perdu une base parce qu’elle avait une copie, dans un bind mount, sans backup derrière et sans moyen de la récupérer. Le correctif n’est pas exotique : un pg_dump nocturne, restic qui l’envoie hors de la machine avec une rétention bornée, et un test de restauration planifié qui prouve que toute la chaîne fonctionne. Faites surtout le test de restauration, parce que c’est l’étape qui sépare ceux qui ont des backups de ceux qui ont de l’espoir. La vue d’ensemble de la série suit le reste.