La vérité douloureuse : la plupart des « sauvegardes » Docker sont des copies optimistes de fichiers que personne n’a jamais restaurées sous pression. La première fois que vous essayez, un disque meurt, un ingénieur est fatigué, et votre VP apprend ce que signifie « RPO ».
Ce guide s’adresse à l’exploitation en production. Pas aux démonstrations. Nous allons sauvegarder les volumes Docker d’une manière qui survive à la réalité, et nous prouverons que les restaurations fonctionnent avec des exercices reproductibles et des contrôles mesurables.
Ce que vous sauvegardez réellement
« Sauvegarder le conteneur » est une phrase qui semble raisonnable et qui est généralement fausse.
Les conteneurs sont des processus jetables plus une image. L’image est (généralement) reconstruisible. Le rayon d’impact réel se trouve dans les données qui vivent en dehors de l’image :
- Volumes nommés (gérés par Docker ; généralement sous
/var/lib/docker/volumes). - Bind mounts (vos chemins hôtes montés dans des conteneurs ; souvent « juste un dossier », jusqu’à ce que ce ne soit plus le cas).
- Secrets/config (variables d’environnement, fichiers montés, secrets Swarm, fichiers Compose, fichiers d’unité systemd).
- Services externes (bases de données managées, stockage d’objets) dont dépend votre conteneur mais qu’il ne contient pas.
Sauvegarder des volumes Docker concerne principalement l’intégrité au niveau système de fichiers et la cohérence au niveau applicatif. L’intégrité des fichiers signifie que les bits sont copiés correctement. La cohérence signifie que l’application peut effectivement lire ces bits après restauration.
Pour les bases de données, « cohérent » n’est pas un ressenti. C’est un état. Vous utilisez soit les outils de sauvegarde natifs de la base, soit vous snapshotez le stockage avec la base correctement quiescée.
Faits intéressants (et pourquoi ils comptent)
- Les volumes Docker ont été conçus pour découpler les données du cycle de vie du conteneur. C’est pourquoi « supprimer le conteneur » ne supprime pas le volume — jusqu’à ce que quelqu’un ajoute
-vsans réfléchir. - AUFS/OverlayFS ont popularisé les couches copy-on-write pour les conteneurs. Super pour les images ; sans rapport pour vos données persistantes, qui vivent dans des volumes ou des bind mounts.
- Les premiers utilisateurs de conteneurs sauvegardaient souvent tout le répertoire
/var/lib/docker. Ça « marchait » jusqu’à ce que les drivers de stockage changent ou que l’hôte de restauration soit différent. La portabilité en a souffert. - Les éditeurs de bases de données prônent les « sauvegardes logiques » depuis des décennies parce que les copies de fichiers physiques pendant l’activité d’écriture peuvent être silencieusement corrompues sans erreurs évidentes au moment de la copie.
- Les snapshots de systèmes de fichiers (ZFS, LVM, btrfs) existaient bien avant les conteneurs. Les conteneurs ont rendu les sauvegardes basées sur snapshots à la mode de nouveau parce qu’ils ont besoin de captures rapides et fréquentes avec un faible overhead.
- Tar est plus ancien que la plupart de votre parc de production. Il est toujours là parce qu’il est simple, streamable et s’intègre proprement avec la compression et le chiffrement.
- RPO/RTO sont devenus du vocabulaire de comité après des incidents médiatisés. Les conteneurs n’ont rien changé ; ils ont juste rendu plus facile la confusion entre « reconstructible » et « récupérable ».
- Les checksums ne sont pas optionnels dans des systèmes de sauvegarde sérieux. La corruption silencieuse existe à tous les niveaux : RAM, disque, contrôleur, réseau, stockage objet. Vérifiez ou soyez surpris.
Principes : faites ceci, pas cela
1) Traitez « sauvegarde » comme un flux de restauration que vous n’avez pas encore exécuté
Un fichier de sauvegarde n’est pas une preuve. Une restauration réussie dans un environnement propre est une preuve. Votre objectif est de réduire l’incertitude, pas de générer des artefacts.
2) Séparez « sauvegarde des données » et « reconstruction du service »
Conservez deux inventaires :
- Inventaire de reconstruction : images, fichiers Compose, configurations système, processus d’émission de certificats TLS, gestion des secrets.
- Inventaire des données : volumes, dumps/WAL/binlogs de bases, fichiers uploadés, files d’attente, index de recherche (et si vous pouvez les reconstruire).
3) Privilégiez les sauvegardes natives applicatives pour les bases de données
Pour PostgreSQL, utilisez pg_dump ou des sauvegardes physiques avec pg_basebackup (et WAL). Pour MySQL/MariaDB, utilisez mysqldump ou des méthodes physiques adaptées à votre moteur. Snapshoter des fichiers bruts de base de données pendant des écritures, c’est jouer à la loterie.
4) Quand vous faites des sauvegardes au niveau système de fichiers, contrôlez l’activité d’écriture
Soit :
- Arrêtez le conteneur applicatif (ou passez-le en maintenance/lecture seule), puis copiez ; ou
- Utilisez des snapshots sur le système de fichiers hôte ; ou
- Utilisez des hooks de quiesce de la base (flush/lock) et snapshottez rapidement.
5) Rendez les sauvegardes adressables par contenu (ou au moins vérifiées par checksum)
Au minimum : stockez un manifeste avec la liste des fichiers + tailles + hachages. « Le fichier existe » n’est pas une vérification.
6) Les tests de restauration doivent être isolés et automatisés
Ne restaurez pas sur le même hôte dans les mêmes chemins et n’annoncer pas victoire. Utilisez un hôte scratch ou une VM jetable. Lancez un contrôle de santé qui prouve que le service lit correctement les données.
Blague #1 : Une sauvegarde que vous n’avez jamais restaurée, c’est comme un parachute que vous n’avez jamais plié — la confiance n’est pas un plan de test.
Une citation, parce qu’elle reste vraie
L’espoir n’est pas une stratégie.
— attribué dans les cercles ops ; traitez-le comme une idée paraphrasée, pas comme une citation garantie exacte.
Tâches pratiques : commandes, sorties, décisions (12+)
Ceux-ci sont volontairement banals. Les pannes en production sont généralement banales aussi.
Task 1: List volumes and spot the ones that matter
cr0x@server:~$ docker volume ls
DRIVER VOLUME NAME
local app_postgres_data
local app_uploads
local monitoring_prometheus
Ce que cela signifie : trois volumes nommés existent. Vos images ne sont pas listées parce que les volumes sont des objets indépendants.
Décision : classifiez chaque volume : « à restaurer impérativement » (bases de données, uploads), « sympa à avoir » (caches), « reconstruisible » (Prometheus peut être reconstruible selon les besoins de rétention).
Task 2: Inspect a volume to find where it lives
cr0x@server:~$ docker volume inspect app_postgres_data
[
{
"CreatedAt": "2025-11-02T12:44:10Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "app"
},
"Mountpoint": "/var/lib/docker/volumes/app_postgres_data/_data",
"Name": "app_postgres_data",
"Options": null,
"Scope": "local"
}
]
Ce que cela signifie : vous utilisez le driver local ; les données sont dans un répertoire sur l’hôte. C’est simple et dangereux : simple à sauvegarder, facile à corrompre si copié à chaud.
Décision : décidez si vous ferez des sauvegardes natives applicatives (préféré pour les bases) ou snapshot/copie froide.
Task 3: Find which containers use a volume (blast radius check)
cr0x@server:~$ docker ps --format '{{.Names}}\t{{.Mounts}}'
app-web /var/lib/docker/volumes/app_uploads/_data
app-db /var/lib/docker/volumes/app_postgres_data/_data
prometheus /var/lib/docker/volumes/monitoring_prometheus/_data
Ce que cela signifie : chaque volume est actuellement monté par un conteneur. C’est bien : moins d’écrivains.
Décision : si plusieurs écrivains montent le même volume, votre plan de cohérence doit être plus explicite (verrous, quiesce, ou refonte).
Task 4: Identify bind mounts hiding in plain sight
cr0x@server:~$ docker inspect app-web --format '{{json .Mounts}}'
[{"Type":"bind","Source":"/srv/app/config","Destination":"/etc/app","Mode":"ro","RW":false,"Propagation":"rprivate"},{"Type":"volume","Name":"app_uploads","Source":"/var/lib/docker/volumes/app_uploads/_data","Destination":"/var/www/uploads","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Ce que cela signifie : vous avez un bind mount sur /srv/app/config. Si vous ne sauvegardez que les volumes Docker, vous manquerez la configuration — alors les restaurations « fonctionnent » mais le service ne démarre pas.
Décision : ajoutez les chemins des bind mounts au périmètre de sauvegarde, ou migrez-les vers un système de configuration géré.
Task 5: Check free space before you generate a giant archive
cr0x@server:~$ df -h /var/lib/docker
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 450G 380G 48G 89% /
Ce que cela signifie : seulement 48G disponibles. Un tarball d’un volume volumineux pourrait remplir le disque et faire tomber Docker avec lui.
Décision : streammez les sauvegardes hors hôte (piped vers un stockage), ou snapshottez et transférez, ou libérez d’abord de l’espace.
Task 6: Get volume size quickly (rough, but useful)
cr0x@server:~$ sudo du -sh /var/lib/docker/volumes/app_postgres_data/_data
23G /var/lib/docker/volumes/app_postgres_data/_data
Ce que cela signifie : ~23G sur disque. La compression peut aider (ou pas, selon les données).
Décision : planifiez la rétention et le temps de transfert. 23G chaque nuit sur une liaison limitée devient vite une excuse hebdomadaire.
Task 7: Cold backup a named volume with tar (safe for non-DB or stopped DB)
cr0x@server:~$ docker stop app-web
app-web
cr0x@server:~$ docker run --rm -v app_uploads:/data:ro -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -cpf /backup/app_uploads.tar .'
cr0x@server:~$ docker start app-web
app-web
Ce que cela signifie : la sauvegarde s’exécute dans un conteneur jetable qui monte le volume en lecture seule et écrit un fichier tar dans /backup (un répertoire hôte que vous devez provisionner).
Décision : si arrêter l’application est inacceptable, passez aux sauvegardes basées sur snapshots ou aux sauvegardes natives applicatives.
Task 8: Add compression and a checksum manifest
cr0x@server:~$ docker run --rm -v app_uploads:/data:ro -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -cpf - . | gzip -1 > /backup/app_uploads.tar.gz'
cr0x@server:~$ sha256sum /backup/app_uploads.tar.gz
9c1e6311d2c51d6f9a9b8b3f5d65ed3db3f87e96a57c4e1b2f5c34b1b1a4d9a0 /backup/app_uploads.tar.gz
Ce que cela signifie : vous avez maintenant une vérification d’intégrité. Stockez la somme à côté de l’artefact dans votre dépôt de sauvegarde.
Décision : si vous ne pouvez pas produire et vérifier des checksums, vous n’avez pas une sauvegarde opérationnelle — juste un fichier.
Task 9: PostgreSQL logical backup from inside the container (preferred for portability)
cr0x@server:~$ docker exec -t app-db sh -c 'pg_dump -U postgres -Fc appdb' > /backup/appdb.dump
cr0x@server:~$ ls -lh /backup/appdb.dump
-rw-r--r-- 1 cr0x cr0x 3.2G Dec 7 02:10 /backup/appdb.dump
Ce que cela signifie : un dump au format custom qui supporte la restauration parallèle et est résilient aux changements de versions mineures (dans une certaine mesure).
Décision : si le dump est beaucoup plus petit que prévu, vérifiez que vous avez dumpé la bonne base et que vous n’avez pas accidentellement dumpé un schéma vide.
Task 10: PostgreSQL restore test into a disposable container (proof, not theory)
cr0x@server:~$ docker run --rm --name pg-restore-test -e POSTGRES_PASSWORD=test -d postgres:16
2f4a6f88d3c8e6d0b0f14a27e8c2e6d84e8c4b7f6ddc5a8c1d2b3a4f5e6d7c8b
cr0x@server:~$ sleep 3
cr0x@server:~$ cat /backup/appdb.dump | docker exec -i pg-restore-test sh -c 'createdb -U postgres appdb && pg_restore -U postgres -d appdb'
cr0x@server:~$ docker exec -t pg-restore-test psql -U postgres -d appdb -c 'select count(*) from users;'
count
-------
10492
(1 row)
cr0x@server:~$ docker stop pg-restore-test
pg-restore-test
Ce que cela signifie : vous avez restauré dans une base propre et exécuté une requête de sanity. C’est une vraie preuve.
Décision : si les comptes ne correspondent pas aux attentes, arrêtez d’appeler ça « vérifié ». Enquêtez avant que la rétention ne fasse tourner votre dernière bonne copie.
Task 11: Restore a named volume tarball into a fresh volume
cr0x@server:~$ docker volume create app_uploads_restore_test
app_uploads_restore_test
cr0x@server:~$ docker run --rm -v app_uploads_restore_test:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -xpf /backup/app_uploads.tar'
cr0x@server:~$ docker run --rm -v app_uploads_restore_test:/data alpine:3.20 sh -c 'ls -lah /data | head'
total 64K
drwxr-xr-x 5 root root 4.0K Dec 7 02:24 .
drwxr-xr-x 1 root root 4.0K Dec 7 02:24 ..
drwxr-xr-x 12 root root 4.0K Dec 5 19:11 images
drwxr-xr-x 3 root root 4.0K Dec 6 08:33 tmp
Ce que cela signifie : vous pouvez décompresser l’archive et voir les répertoires de premier niveau attendus.
Décision : si les permissions/possessions importent (et elles importent), validez-les avec un fichier représentatif et assurez-vous que votre tar les a préservées (-p aide quand vous êtes root).
Task 12: Verify archive integrity before restore (catch corrupted transfers)
cr0x@server:~$ sha256sum -c /backup/app_uploads.tar.gz.sha256
/backup/app_uploads.tar.gz: OK
Ce que cela signifie : l’artefact correspond au hash attendu.
Décision : si vous voyez FAILED, arrêtez. Ne restaurez pas de la marchandise corrompue en production. Récupérez une autre sauvegarde.
Task 13: Detect open files to judge whether a “live copy” is risky
cr0x@server:~$ sudo lsof +D /var/lib/docker/volumes/app_postgres_data/_data | head
postgres 22114 999 15u REG 259,2 16777216 393222 /var/lib/docker/volumes/app_postgres_data/_data/base/16384/2619
postgres 22114 999 16u REG 259,2 16777216 393223 /var/lib/docker/volumes/app_postgres_data/_data/base/16384/2620
Ce que cela signifie : PostgreSQL écrit activement. Copier ces fichiers maintenant n’est pas une sauvegarde ; c’est un générateur de corruption.
Décision : utilisez pg_dump/pg_basebackup ou arrêtez/quiescez + snapshot.
Task 14: Measure restore throughput (know your RTO, don’t guess)
cr0x@server:~$ time docker run --rm -v app_uploads_restore_test:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && rm -rf ./* && tar -xpf /backup/app_uploads.tar'
real 0m18.412s
user 0m0.812s
sys 0m3.951s
Ce que cela signifie : votre restauration a pris ~18 secondes pour ce jeu de données sur cet hôte. C’est le nombre que vous utilisez pour planifier le RTO (plus le warmup du service).
Décision : si la restauration est lente, n’optimisez pas au hasard — suivez le mode opératoire de diagnostic rapide.
Méthodes de sauvegarde qui tiennent la route
Method A: Logical backups for databases (recommended)
Si vous avez un volume de base de données et que vous le tar.gz « parce que c’est facile », arrêtez. Utilisez le mécanisme de sauvegarde de la base. Vous obtenez :
- Portabilité entre hôtes et drivers de stockage
- Cohérence garantie par le moteur
- Meilleure capacité de dépannage : la restauration vous dira ce qui ne va pas
Pour PostgreSQL, une base solide est un pg_dump -Fc quotidien plus l’archivage WAL si vous avez besoin de recovery point-in-time. Pour MySQL, une base est mysqldump ou des sauvegardes physiques spécifiques au moteur avec binlogs.
Compromis : les sauvegardes logiques peuvent être plus lentes et plus volumineuses pour certains workloads, et les restaurations peuvent être plus lentes que des restaurations au niveau fichier. C’est une décision métier — mais prenez-la explicitement.
Method B: “Cold” filesystem backup of a volume (stop the writer)
Pour les uploads, configs, artefacts et données non transactionnelles : arrêter le conteneur (ou s’assurer qu’il n’y a pas d’écritures) et copier le volume est simple.
- Avantages : simple, rapide, facile à comprendre
- Inconvénients : nécessite une fenêtre d’indisponibilité ou un gel d’écriture ; doit préserver ownership/ACL/xattrs si pertinent
Si votre appli utilise des capacités Linux, des labels SELinux ou des ACLs, votre commande tar doit les préserver. Le tar d’Alpine suffit pour les permissions de base ; si vous avez besoin d’xattrs/ACLs, utilisez un conteneur de backup avec GNU tar et les flags adaptés à votre environnement.
Method C: Snapshot-based backups on the host filesystem (fast, low downtime)
Si votre répertoire de données Docker vit sur ZFS, LVM-thin ou btrfs, vous pouvez snapshotter le dataset/volume sous-jacent rapidement, puis copier depuis le snapshot pendant que la production continue.
Important : snapshotter un système de fichiers ne rend pas magiquement une application cohérente. Pour les bases, combinez snapshots avec un quiesce approprié ou le mode de sauvegarde du moteur, sinon vous pouvez snapshotter un système de fichiers parfaitement cohérent contenant un état de base de données parfaitement incohérent.
Method D: Remote volume drivers / network storage
Certaines équipes utilisent NFS, iSCSI, Ceph ou du stockage bloc cloud derrière un driver de volume Docker. Ça peut être correct, mais cela déplace le problème de sauvegarde :
- Vous sauvegardez maintenant le système de stockage, pas Docker.
- Les restaurations peuvent exiger le même driver et la même configuration.
- La latence et le comportement en petites écritures peuvent nuire aux bases de données.
Quand vous utilisez un driver distant, consignez le nom du driver, les options et le cycle de vie. Si votre plan de restauration commence par « on va juste rattacher le volume », vous avez besoin d’un plan B pour le cas où ce système est celui qui brûle.
Method E: Image-based “backup” (the trap)
Des gens proposeront « docker commit le conteneur ». Cela produit une couche d’image contenant le système de fichiers du conteneur à ce moment. Ça ne capture pas les volumes nommés. Ça capture rarement les bind mounts. C’est aussi un bon moyen de conserver des secrets dans une image pour toujours. Ne le faites pas pour des sauvegardes de données.
Blague #2 : « On a sauvegardé le conteneur » est la manière d’obtenir une belle image d’un service qui a tout oublié de ce qu’il savait autrefois.
Comment prouver que les restaurations fonctionnent (pas juste « ça s’est extrait »)
Définissez « fonctionne » comme un contrat testable
Une restauration « fonctionne » lorsque :
- Les données se restaurent dans un environnement propre sans bidouille manuelle.
- Le service démarre avec les données restaurées.
- Un petit ensemble de contrôles de comportement passe : les requêtes retournent les lignes attendues, les uploads sont lisibles, les migrations se comportent, et les logs n’indiquent pas de corruption.
- L’équipe peut le faire sous pression temporelle avec un runbook.
Construisez un exercice de restauration qui s’exécute selon un calendrier
Choisissez une cadence soutenable : hebdomadaire pour les données critiques, mensuelle pour les moins critiques, après chaque changement majeur de schéma. L’exercice doit :
- Télécharger l’artefact de sauvegarde le plus récent.
- Vérifier les checksums.
- Restaurer dans une infrastructure jetable (VM, hôte éphémère, ou réseau Docker isolé).
- Exécuter une suite de validation courte.
- Publier les résultats quelque part visible (ticket, canal Slack, dashboard), y compris les raisons des échecs.
Suite de validation : exemples qui détectent vraiment les problèmes
- Base de données : exécuter des requêtes
SELECTpour les comptes de lignes dans les tables clés ; vérifier que les migrations peuvent s’exécuter en mode dry-run si disponible ; confirmer l’existence des index ; confirmer des timestamps récents. - Uploads : choisir 10 fichiers connus et vérifier leur checksum ou au moins leur taille ; s’assurer que les permissions permettent à l’utilisateur de l’application de les lire.
- Démarrage de l’application : vérifier les logs pour des motifs d’erreur connus (permission refusée, clé de config manquante, mismatch de schéma).
Prouvez le RPO et le RTO, pas seulement la correction
La correction répond à « pouvons‑nous restaurer ? » RPO/RTO répond à « pouvons‑nous restaurer à temps, avec une perte de données acceptable ? » Mesurez :
- RPO : temps entre la dernière sauvegarde réussie et le moment de l’incident (utilisez les horodatages des sauvegardes, pas les impressions).
- RTO : temps depuis « on commence la restauration » jusqu’à « le service retrouve son SLO ». Incluez le temps pour récupérer les artefacts, décompresser, vérifier et réchauffer les caches.
Conservez un « kit de restauration » avec tout ce qui n’est pas les données
La plupart des restaurations échouent pour des raisons autres que des données « mauvaises ». Il manque souvent des éléments :
- Fichier Compose versionné et stocké
- Images de conteneurs versionnées (ou pipeline de build reproductible)
- Gestion des secrets documentée
- Réseau/ports, configuration reverse proxy, processus de renouvellement TLS
- Notes de compatibilité de versions de base
Mode opératoire de diagnostic rapide
Les sauvegardes et restaurations échouent de façons prévisibles. Ne commencez pas par réécrire vos scripts à l’aveugle. Commencez par restreindre le goulet d’étranglement.
Première étape : l’échec concerne‑t‑il la cohérence ou la mécanique ?
- Mécanique : tar échoue, checksum mismatch, permission refusée, manque d’espace, transfert lent.
- Cohérence : la restauration se termine mais l’appli affiche des erreurs, la DB signale une corruption, données récentes manquantes.
Deuxième étape : identifiez l’étape la plus lente
- Récupération de l’artefact (réseau/stockage objet)
- Décompression (lié au CPU)
- Extraction/écriture (lié au disque, aux inodes)
- Récupération applicative (relecture WAL, migrations)
Troisième étape : contrôles rapides qui donnent des réponses
Vérifier la saturation disque (la restauration écrit beaucoup)
cr0x@server:~$ iostat -xz 1 3
Linux 6.5.0 (server) 12/07/2025 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
12.31 0.00 8.14 34.22 0.00 45.33
Device r/s rkB/s rrqm/s %rrqm r_await rareq-sz w/s wkB/s wrqm/s %wrqm w_await wareq-sz aqu-sz %util
nvme0n1 12.0 1456.0 0.0 0.0 1.20 121.3 980.0 84224.0 120.0 10.9 18.50 85.9 18.2 98.0
Ce que cela signifie : %util proche de 100% et w_await élevé implique que le disque est le goulot.
Décision : réduisez le parallélisme de restauration, restaurez sur un stockage plus rapide, ou évitez les formats de compression qui amplifient massivement les écritures.
Vérifier que le CPU n’est pas saturé par la décompression
cr0x@server:~$ top -b -n 1 | head -n 15
top - 02:31:20 up 21 days, 4:12, 1 user, load average: 7.92, 8.10, 6.44
Tasks: 212 total, 2 running, 210 sleeping, 0 stopped, 0 zombie
%Cpu(s): 92.1 us, 0.0 sy, 0.0 ni, 5.8 id, 0.0 wa, 0.0 hi, 2.1 si, 0.0 st
MiB Mem : 32158.5 total, 812.4 free, 14220.1 used, 17126.0 buff/cache
MiB Swap: 0.0 total, 0.0 free, 0.0 used. 17938.4 avail Mem
Ce que cela signifie : le CPU est saturé en espace utilisateur ; la décompression ou le calcul de checksum peut être le point chaud.
Décision : utilisez une compression plus rapide (gzip -1 vs forte), des outils de décompression parallèles, ou stockez non compressé sur des réseaux internes rapides quand le disque est de toute façon le goulot.
Vérifier l’épuisement des inodes (classique pour beaucoup de petits fichiers)
cr0x@server:~$ df -ih /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 28M 27M 1.0M 97% /
Ce que cela signifie : vous pouvez avoir de l’espace libre mais plus d’inodes ; les restaurations échouent avec « No space left on device » alors que df -h semble correct.
Décision : déplacez les volumes vers un système de fichiers avec plus d’inodes ou ajustez les paramètres de création du système de fichiers ; réduisez le churn de petits fichiers ; envisagez d’emballer dans du stockage objet là où c’est approprié.
Vérifier un mismatch de permission/possession (fréquent après des restores tar)
cr0x@server:~$ docker exec -t app-web sh -c 'id && ls -ld /var/www/uploads'
uid=1000(app) gid=1000(app) groups=1000(app)
drwxr-xr-x 5 root root 4096 Dec 7 02:24 /var/www/uploads
Ce que cela signifie : l’appli s’exécute en UID 1000, mais le répertoire est possédé par root. Les lectures peuvent fonctionner ; les écritures échoueront.
Décision : corrigez la possession dans la procédure de restauration (par ex. chown -R 1000:1000), ou exécutez backup/restore en préservant la possession et assurez la correspondance des utilisateurs.
Erreurs courantes : symptôme → cause racine → correction
1) « La restauration est terminée, mais l’appli manque des données récentes »
Symptôme : le service démarre, mais les données des dernières 24h/semaines ont disparu.
Cause racine : vous avez sauvegardé le mauvais volume, la mauvaise base, ou le mauvais environnement ; ou votre job de sauvegarde a silencieusement échoué et vous avez continué à faire tourner des artefacts vides.
Correction : appliquez une cartographie d’inventaire (volume/base → nom d’artefact). Faites échouer le job si la taille du dump est sous un seuil. Lancez des drills de restauration programmés avec des requêtes de sanity.
2) « Le backup tar existe, mais la restauration donne permission denied »
Symptôme : les logs de l’appli montrent des erreurs de permission en écriture sur les répertoires restaurés.
Cause racine : restauration en root sans correspondance UID/GID attendue ; ACLs/xattrs perdues ; ou conteneurs tournant en non-root.
Correction : capturez et restaurez explicitement la possession ; incluez une étape de restauration qui applique UID/GID corrects ; validez avec un test d’écriture en tant qu’utilisateur de l’appli.
3) « La base de données ne démarre pas après copie de volume »
Symptôme : PostgreSQL ou MySQL refuse de démarrer, se plaint du WAL/binlog ou de pages corrompues.
Cause racine : copie au niveau fichier prise pendant que la DB écrivait ; snapshot incomplet ; état incohérent.
Correction : utilisez une sauvegarde logique ou une sauvegarde physique adaptée. Si vous devez snapshotter, quiescez correctement et snapshottez atomiquement.
4) « Le job de sauvegarde met une éternité et provoque des pics de latence »
Symptôme : la latence I/O en production augmente pendant les sauvegardes ; l’appli ralentit.
Cause racine : les lectures de backup concurrencent les lectures de production ; la compression sollicite trop le CPU ; le stockage est saturé ; trop de petits fichiers.
Correction : limitez le débit de lecture du backup ; planifiez hors‑pic ; utilisez des snapshots ; changez le niveau de compression ; refactorez la disposition des données.
5) « Nous avons restauré le volume, mais l’appli pointe encore vers l’ancienne donnée »
Symptôme : la restauration semble réussie mais le service renvoie du contenu obsolète.
Cause racine : vous avez restauré dans un nouveau volume mais le fichier Compose référence toujours l’ancien ; ou les chemins de bind mount diffèrent.
Correction : échangez explicitement les références de volume ; utilisez des noms de projet de test uniques ; confirmez les montages de conteneur avec docker inspect.
6) « La vérification checksum échoue occasionnellement »
Symptôme : mismatches de hash aléatoires selon les jours.
Cause racine : uploads partiels, écritures non atomiques dans le store de sauvegarde, ou transferts réseau instables.
Correction : écrivez sur un nom temporaire puis renommez atomiquement ; stockez et vérifiez des manifestes ; assurez-vous que votre outil d’upload utilise une vérification multipart ; retry sur mismatch et alertez.
Trois mini-histoires d’entreprise (anonymisées)
Mini‑histoire 1 : Un incident causé par une mauvaise hypothèse
L’équipe faisait tourner une appli client avec un conteneur PostgreSQL et un volume nommé. Leur script de sauvegarde tarait le volume la nuit. Ça semblait propre. Il y avait même des horodatages et de la rétention. Tout le monde dormait bien.
Puis un on‑call a été notifié d’une panne d’hôte. Le plan de récupération était simple : provisionner une nouvelle VM, restaurer le tarball dans un volume frais, démarrer le conteneur DB. Le conteneur a démarré… et a immédiatement crashé. Les logs parlaient de problèmes WAL et d’un état de base de données qui « ressemble à une copie prise pendant qu’il tournait ». Parce que c’était le cas.
L’hypothèse erronée était subtile : « une copie du système de fichiers est une sauvegarde ». Le script tournait à 2 h du matin quand la charge était faible, mais pas nulle. PostgreSQL écrivait encore. La copie a produit une archive interne incohérente qu’un tar ne pouvait jamais détecter.
Ils ont finalement récupéré une sauvegarde plus ancienne qui, par chance, était cohérente (la nuit la plus calme du mois). Ensuite, ils sont passés à pg_dump plus un drill de restauration hebdomadaire dans un conteneur jetable. La partie ennuyeuse — tester les restaurations — est devenue celle qui importait aux dirigeants.
Mini‑histoire 2 : Une optimisation qui s’est retournée contre eux
Une autre organisation avait une montagne d’uploads utilisateurs. Les sauvegardes étaient trop lentes, alors quelqu’un les « a optimisées » avec une compression maximale pour économiser bande passante et stockage. Les artefacts ont rétréci de façon impressionnante. Tout le monde aimait le graphique de coûts.
Les restaurations n’avaient jamais été testées à grande échelle. La première vraie restauration est survenue lors d’un incident de sécurité où il fallait reconstruire des hôtes rapidement. Ils ont tiré la sauvegarde et commencé à décompresser. Le CPU a plafonné. Les écritures disque se sont enchaînées. Le pipeline de restauration a pris des heures de plus que le RTO qu’ils avaient fièrement annoncé dans une présentation.
Le problème technique n’était pas que la compression soit mauvaise. C’était qu’ils avaient optimisé un seul indicateur (octets stockés) sans mesurer le temps de restauration. Ils décompressaient aussi sur des nœuds de récupération contraints en CPU, faisant de la récupération une tâche liée au compute.
La correction était peu glamour : passer à une compression rapide (ou aucune) pour les sauvegardes hot-tier, et garder une copie plus compressée pour l’archivage. Ils ont aussi commencé à mesurer le débit de restauration comme métrique prioritaire. Les coûts ont un peu augmenté. La fatigue d’astreinte a beaucoup diminué.
Mini‑histoire 3 : Une pratique ennuyeuse mais correcte qui a sauvé la journée
Un SaaS orienté finance faisait tourner plusieurs services avec Docker Compose. Leurs runbooks incluaient un ticket hebdomadaire de « répétition de restauration ». L’on‑call restaurait le dump DB le plus récent dans un conteneur jetable, montait la stack applicative dans un réseau isolé, et exécutait une poignée d’appels API.
Pas d’héroïsme. Juste de la répétition. Ils consignaients les temps : temps de téléchargement, temps de restauration, temps de première requête réussie. Si les mesures dérivaient, ils enquêtaient pendant qu’il n’y avait pas de panique.
Un week‑end, un incident de stockage a corrompu un système de fichiers hôte. Ils ont reconstruit un nœud, restauré volumes et dumps DB, et étaient de nouveau en ligne sans drame. Ce qui semblait être de la chance était juste de la mémoire musculaire. L’équipe savait déjà quels artefacts étaient valides, combien de temps la restauration prenait, et quelles commandes échoueraient si quelque chose n’allait pas.
Le vrai gain : pas d’improvisation. Ils ont exécuté un workflow pratiqué et sont retournés à un état d’agacement modéré face aux alertes, ce qui est l’état émotionnel idéal pour l’astreinte.
Listes de contrôle / plan étape par étape
Checklist A: Build your backup inventory (one afternoon)
- Lister les volumes :
docker volume ls. - Lister les bind mounts :
docker inspectsur chaque conteneur et extraireMounts. - Classer les données : base / uploads / cache / reconstruisible.
- Définir RPO et RTO par classe (même approximatif c’est mieux que silence).
- Noter les exigences d’ownership/permissions (UID/GID, ACLs, SELinux).
Checklist B: Implement backups (repeatable scripts)
- Pour les bases : implémentez une sauvegarde logique (ou physique correcte) et stockez les artefacts hors hôte.
- Pour les volumes de fichiers : choisissez copie froide ou snapshot ; évitez la copie à chaud pour les écrivains.
- Créer un manifeste par artefact : horodatage, source, taille, checksum, version de l’outil.
- Rendre les sauvegardes atomiques : écrire temp puis renommer ; ne laissez jamais d’artefacts partiels avec des noms « finaux ».
- Alertes sur échecs et sorties anormalement petites.
Checklist C: Prove restores (weekly or monthly drill)
- Télécharger l’artefact le plus récent.
- Vérifier le checksum.
- Restaurer dans un environnement propre (nouveau volume/nouveau conteneur/nouveau projet Compose).
- Exécuter les contrôles de validation (requêtes, vérifs de fichiers, endpoints de santé).
- Enregistrer les temps et résultats ; ouvrir un ticket pour toute déviation.
Checklist D: Incident restore runbook (when it’s already bad)
- Arrêter l’hémorragie : empêcher les écrivains de continuer (mode maintenance, arrêt des conteneurs).
- Identifier la « dernière bonne » sauvegarde à partir des logs de drill, pas de l’espoir.
- Restaurer dans de nouveaux volumes ; ne pas écraser les preuves sauf nécessité.
- Remonter les services dans l’ordre des dépendances (DB d’abord, puis appli, puis workers).
- Valider depuis l’extérieur (checks synthétiques) et l’intérieur (logs, checks d’intégrité DB).
- Après récupération : conservez le disque/volume défaillant pour forensique si besoin.
FAQ
1) Should I back up /var/lib/docker?
Généralement non. Ce n’est pas portable entre drivers de stockage, versions Docker et dispositions d’hôte. Sauvegardez les données (volumes et bind mounts) et les définitions (fichiers Compose, configs) séparément.
2) Is a tar of a volume always safe?
Sûr si les données ne sont pas modifiées ou si l’application tolère des copies crash‑consistantes. Pour les bases, supposez que ce n’est pas sûr sauf si vous quiescez correctement ou utilisez des sauvegardes natives DB.
3) What’s the difference between a named volume and a bind mount for backups?
Les volumes nommés sont gérés par Docker et vivent sous le répertoire de données Docker. Les bind mounts sont des chemins hôtes arbitraires. Du point de vue sauvegarde, les bind mounts sont plus faciles à intégrer aux outils de sauvegarde hôte — jusqu’à ce que quelqu’un change le chemin et oublie de mettre à jour le périmètre de sauvegarde.
4) How do I back up volumes with Docker Compose?
Compose n’est que de l’orchestration. La mécanique de sauvegarde est la même : utilisez docker exec pour les sauvegardes logiques DB, et docker run --rm avec des mounts de volume pour les sauvegardes système de fichiers. L’important est la cohérence des noms pour que vos scripts trouvent les bons volumes dans chaque environnement.
5) Can I use docker commit as a backup?
Non pour les données persistantes. Cela n’inclura pas les volumes nommés, et peut capturer des secrets dans une couche d’image. C’est parfois utile pour déboguer l’état d’un filesystem de conteneur, pas pour la reprise après sinistre.
6) How often should I test restores?
Autant que votre activité peut se permettre d’être erronée. Hebdomadaire pour les bases de données cœur est courant ; mensuel pour les datasets moins critiques. Testez aussi après des changements majeurs de schéma, des migrations de stockage ou des rebuilds d’hôte Docker.
7) Do I need encryption for volume backups?
Si les sauvegardes contiennent des données clients, des identifiants ou du code propriétaire, oui. Chiffrez au repos et en transit, et gérez les clés séparément du stockage de sauvegarde. « C’est dans un bucket privé » n’est pas un contrôle, c’est de l’espoir.
8) How do I handle UID/GID differences across hosts?
Privilégiez des IDs numériques stables pour les utilisateurs de service entre les hôtes. Si ce n’est pas possible, incluez une étape de correction après restauration. Vérifiez en effectuant un test d’écriture en tant qu’utilisateur d’exécution du conteneur, pas en root.
9) What about incremental backups for huge volumes?
Faisable, mais la complexité a un coût. Pour les volumes de fichiers, l’envoi/incremental de snapshots (ZFS/btrfs) ou des incrémentiels basés sur rsync peuvent fonctionner. Pour les bases, utilisez WAL/binlogs ou l’outillage du fournisseur. Quoi que vous choisissiez, votre drill de restauration doit inclure la reconstruction depuis les incrémentiels — sinon votre « stratégie incrémentale » est théorique.
10) What’s the single most reliable improvement I can make?
Automatisez la vérification de restauration dans un environnement isolé. Cela transforme le « on pense » en « on sait », et détecte les échecs silencieux comme les dumps vides, les mauvaises cibles et les mismatches de permission.
Conclusion : prochaines étapes réalisables dès aujourd’hui
- Inventoriez vos données : volumes, bind mounts, et « trucs hors Docker » (secrets, configs).
- Choisissez les bons primitives de sauvegarde : DB-native pour les bases ; cold/snapshot pour les données fichiers.
- Ajoutez de l’intégrité : checksums et manifestes, stockés avec les artefacts.
- Planifiez un drill de restauration : restaurez dans un conteneur/stack jetable et exécutez des vérifications réelles.
- Mesurez le RTO : chronométrez la restauration de bout en bout, puis décidez si c’est acceptable.
Si vous ne faites qu’une seule chose : restaurez une sauvegarde dans un environnement propre cette semaine et faites-en une habitude. Votre futur bridge d’incident sera plus calme, plus court et bien moins théâtral.