Vous ne réalisez pas combien de votre application est « juste des fichiers » avant d’essayer de la déplacer. Un hôte meurt, une équipe cloud « aide »
remplace des instances, ou vous décidez de changer de distribution parce qu’une CVE a fait disparaître vos plans de week-end. Soudain vos conteneurs
sont portables et vos données ne le sont pas.
Les bind mounts et les volumes nommés ressemblent tous deux à du « stockage persistant » dans Docker. Ils ne sont pas équivalents. L’un migre comme un
système de fichiers normal. L’autre migre comme un détail d’implémentation Docker. Choisissez mal, et la migration fonctionne — jusqu’à la première fois
où vous en aurez besoin.
La thèse tranchée : qu’est-ce qui survit mieux aux migrations
Si votre priorité absolue est « je peux déplacer le stockage de cet hôte et le ré-héberger avec un minimum de drame »,
les bind mounts survivent mieux aux migrations — lorsque vous contrôlez la disposition du système de fichiers de l’hôte.
Ils sont explicites, visibles et transférables avec les mêmes outils en lesquels vous avez déjà confiance pour le déplacement des données : rsync, snapshots,
sauvegardes, réplication, restaurations, sommes de contrôle. Vous pouvez les raisonner sans demander la permission à Docker.
Si votre priorité est « je veux que Docker gère l’emplacement et le cycle de vie du stockage et je suis d’accord pour le traiter comme
un artefact applicatif », les volumes nommés conviennent — et sont souvent plus simples pour les opérations day-2 sur un même hôte.
Ils réduisent les tirs dans le pied comme les fautes de frappe dans les chemins, et ils sont plus propres dans les stacks Compose. Mais ils ne migrent pas automatiquement bien.
Vous devez les exporter/importer volontairement.
Par défaut en production je choisis la solution ennuyeuse :
-
Bases de données et services à état : bind mount vers un répertoire hôte qui repose sur un support de stockage réel
que vous sauvegardez déjà (LVM, dataset ZFS, volume EBS, LUN SAN, bref ce que votre organisation appelle « stockage »). - Données applicatives modifiables et reconstruisables : les volumes nommés sont acceptables, parfois préférés.
- Tout ce que vous pourriez devoir restaurer sous pression : évitez le stockage « uniquement Docker » à moins d’avoir répété les exportations de volumes.
Une opinion de plus : les migrations échouent parce que les gens confondent « portabilité du conteneur » avec « portabilité des données ».
Le conteneur est la partie facile. Les octets sont le boulot.
Ce qui se passe réellement sur le disque
Bind mounts : Docker en tant qu’invité, votre système de fichiers comme source de vérité
Un bind mount, c’est Docker qui dit : « Prenez ce /path/on/host et présentez-le à /path/in/container. »
Docker ne possède pas les données. Docker ne peut même pas faire semblant. Le chemin hôte existe, les permissions s’appliquent, les politiques SELinux/AppArmor
s’appliquent, et les snapshots ou la réplication de votre équipe stockage s’appliquent.
Implication pour la migration : si vous pouvez déplacer ce chemin hôte — en déplaçant le disque sous-jacent, snapshotant un dataset, restaurant une sauvegarde,
ou en rsyncant des répertoires — vous pouvez déplacer les données. Docker n’ajoute pas de format spécial.
Volumes nommés : Docker en tant que propriétaire (et vous louez l’espace)
Un volume nommé, c’est Docker qui dit : « Je vais allouer du stockage pour ce volume, géré par un driver de volume. »
La plupart du temps, le driver est local. Sur Linux avec le moteur par défaut, cela signifie souvent que Docker stocke les données de volume sous :
/var/lib/docker/volumes/<volume>/_data.
Ce chemin n’est pas une API stable. C’est un détail d’implémentation qui change selon :
le driver de stockage, le mode rootless, la couche VM de Docker Desktop, le packaging de la distribution, et la prochaine politique que votre équipe sécurité imposera.
Implication pour la migration : pour déplacer des volumes nommés en toute sécurité, traitez-les comme un actif géré par Docker :
inspecter, exporter, déplacer, importer, vérifier. Ne « copiez pas juste /var/lib/docker » à moins d’avoir testé exactement cette version du moteur,
le système de fichiers et la combinaison du driver de stockage. Parfois ça marche. Parfois ça marche jusqu’à ce que ça ne marche plus.
Blague n°1 : Les volumes Docker sont comme les plantes de bureau : personne ne se rappelle qui les possède jusqu’au moment de déménager.
Faits et contexte historique (ce qui explique le bordel d’aujourd’hui)
-
Docker encourageait à l’origine les conteneurs éphémères (culture vers 2013) : « reconstruire, ne pas patcher. »
Les données persistantes étaient volontairement externalisées, ce qui explique pourquoi les volumes semblent greffés plutôt que natifs. -
Les volumes nommés étaient une correction d’usabilité pour le « bind-mount everything », devenu désordonné dans les fichiers Compose et fragile entre hôtes
ayant des layouts de répertoires différents. -
Le driver de volume
localn’est pas un contrat universel. Sur Linux il mappe au système de fichiers hôte ;
sur Docker Desktop il mappe à un système de fichiers dans une VM que vous ne contrôlez pas directement de la même façon. -
Les drivers de stockage ont évolué (AUFS → overlay2 devenu courant). Le comportement de « copier le répertoire de données de Docker »
est fortement lié au format sur disque du driver de stockage. -
Compose v2 a rendu les « volumes nommés par défaut » plus courants parce que c’est pratique et évite les hypothèses sur les chemins.
La commodité est la façon dont on accumule une dette technique en souriant. -
Rootless Docker et les user namespaces ont fait passer les problèmes de permissions de volumes de « cas rare » à « surprise de routine »,
surtout avec les bind mounts où le mapping UID/GID compte. -
L’étiquetage SELinux a été un piège de migration persistant. Déplacer des données bind-montées entre hôtes avec des contextes SELinux différents
peut casser des workloads même si les octets sont corrects. -
Kubernetes a normalisé l’idée de volumes persistants explicites avec drivers et claims. Les volumes nommés Docker ressemblent à cela,
mais ils n’ont pas la même histoire de migration ni le même niveau d’abstraction.
Matrice de survie aux migrations : bind mounts vs volumes nommés
Ce que « survivre aux migrations » veut vraiment dire
« Survivre » fait beaucoup de boulot ici. Une migration est réussie quand :
- Les données arrivent intactes (les sommes de contrôle correspondent, pas seulement « ça démarre »).
- Les permissions et la propriété sont correctes pour le modèle d’exécution du conteneur.
- Les caractéristiques de performance ne sont pas catastrophiquement différentes (IOPS, latence fsync, comportement du cache de pages).
- Les workflows opérationnels fonctionnent toujours (sauvegardes, restaurations, débogage d’incident).
Bind mounts : forces et modes d’échec
Forces :
- Directement migrables avec des outils standards.
- Transparents : vous pouvez voir les données sans Docker en marche.
- Compatibles avec les fonctionnalités de stockage au niveau hôte : snapshots, réplication, quotas, chiffrement, déduplication, compression.
- Mieux pour la conformité quand les auditeurs demandent « Où sont les données ? » et que vous voulez répondre sans cours sur Docker.
Modes d’échec :
-
Couplage au chemin : votre stack suppose que
/srv/app/dbexiste sur chaque hôte.
Si vous remplacez un hôte et oubliez ce répertoire (ou son montage), le conteneur démarre sur un répertoire vide. Félicitations, vous avez inventé la perte de données. -
Incompatibilité de permissions : le user du conteneur attend UID 999 mais vos fichiers restaurés sont possédés par 1001.
« Permission denied » est la version polie de ça. - Friction SELinux/AppArmor : les octets sont présents, l’accès est bloqué. C’est le comportement correct le plus agaçant sous Linux.
-
Exposition accidentelle de l’hôte : un bind mount de
/ou/var/run/docker.sockest un incident de sécurité en puissance.
Volumes nommés : forces et modes d’échec
Forces :
- Portabilité de la configuration : les fichiers Compose n’encodent pas des chemins hôtes.
- Plus sûrs par défaut : moins de catastrophes « oups j’ai monté le mauvais répertoire ».
- Isolation : les données sont rangées sous la gestion de Docker, réduisant les manipulations accidentelles.
- Flexibilité des drivers : vous pouvez utiliser des plugins pour pointer vers NFS, des blocs, ou du stockage cloud (mais maintenant vous administrez aussi un driver).
Modes d’échec :
-
La migration nécessite des étapes conscientes relatives à Docker. Si vous traitez les données de volume comme un blob opaque sous
/var/lib/docker,
vous héritez de toute la complexité interne de Docker. - Dépendance cachée sur la configuration du moteur : rootless, VM Desktop, driver de stockage, choix du système de fichiers.
-
Angles morts de sauvegarde : les outils d’infrastructure excluent souvent
/var/lib/dockerparce que c’est « reconstruisible ».
Vos volumes nommés peuvent vivre dans la zone exclue.
Alors, qu’est-ce qui survit mieux ?
Dans les migrations, l’explicite bat l’implicite. Les bind mounts sont explicites : vous pouvez les voir et les déplacer.
Les volumes nommés peuvent très bien survivre si vous operationalisez les exportations et les restaurations et que vous ne dépendez pas de chemins non documentés.
La plupart des équipes ne répètent pas cela. Elles répètent « redéployer des conteneurs », pas « ré-héberger l’état ».
Tâches pratiques (commandes, sorties et décisions)
Voici la partie où vous arrêtez de deviner. Chaque tâche inclut : une commande, ce que la sortie signifie, et la décision que vous prenez.
Exécutez-les sur l’hôte source avant la migration, et de nouveau sur l’hôte de destination après.
Tâche 1 : Lister les conteneurs et leurs montages (trouver ce qui est réellement avec état)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Mounts}}'
NAMES IMAGE MOUNTS
pg01 postgres:16 pgdata
api01 myorg/api:2.4.1 /srv/api/config:/app/config
grafana01 grafana/grafana:11 grafana-storage
Signification : la colonne mounts mélange volumes nommés (par ex., pgdata) et bind mounts (les chemins hôtes apparaissent comme host:container).
Décision : tout ce qui contient l’état métier reçoit un plan de migration. « C’est juste un cache » n’est pas un plan.
Tâche 2 : Inspecter les montages d’un conteneur (la vérité, pas les impressions)
cr0x@server:~$ docker inspect pg01 --format '{{json .Mounts}}'
[{"Type":"volume","Name":"pgdata","Source":"/var/lib/docker/volumes/pgdata/_data","Destination":"/var/lib/postgresql/data","Driver":"local","Mode":"z","RW":true,"Propagation":""}]
Signification : Type vous indique bind vs volume. Source pour un volume nommé local pointe souvent sous le répertoire de données de Docker.
Décision : si c’est un volume nommé, planifiez export/import. Si c’est un bind mount, planifiez la migration du système de fichiers et les vérifications de permissions.
Tâche 3 : Inspecter un volume nommé (driver, point de montage, labels)
cr0x@server:~$ docker volume inspect pgdata
[
{
"CreatedAt": "2025-11-02T09:14:21Z",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "billing",
"com.docker.compose.volume": "pgdata"
},
"Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
"Name": "pgdata",
"Options": null,
"Scope": "local"
}
]
Signification : Scope: local signifie « ceci n’est pas partagé magiquement ». Mountpoint est l’endroit où les octets vivent sur cet hôte.
Décision : si vous attendiez un stockage HA, vous ne l’avez pas. Si vous attendiez une « migration automatique », vous ne l’avez pas non plus.
Tâche 4 : Confirmer que le chemin hôte derrière un bind mount existe et est monté correctement
cr0x@server:~$ ls -ld /srv/api/config
drwxr-x--- 2 root api 4096 Jan 2 09:10 /srv/api/config
cr0x@server:~$ findmnt -T /srv/api/config
TARGET SOURCE FSTYPE OPTIONS
/srv /dev/nvme0n1p2 ext4 rw,relatime
Signification : les données du bind mount vivent sur le système de fichiers qui soutient ce chemin ; ici c’est ext4 sur une partition NVMe locale.
Décision : si findmnt montre la mauvaise source (ou rien), corrigez les montages avant de migrer. Sinon vous migrerez le répertoire vide de substitution.
Tâche 5 : Vérifier l’espace libre et les inodes (les migrations échouent parce que vous manquez de « pas d’espace »)
cr0x@server:~$ df -h /srv /var/lib/docker
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 200G 120G 71G 63% /srv
/dev/nvme0n1p3 100G 92G 3.5G 97% /var/lib/docker
cr0x@server:~$ df -i /var/lib/docker
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p3 655360 621104 34256 95% /var/lib/docker
Signification : la partition de données Docker est presque pleine et en manque d’inodes. C’est une configuration classique où « les choses deviennent bizarres ».
Décision : ne migrez pas un bazar vers un nouvel hôte. Corrigez la capacité d’abord ou changez l’endroit où Docker stocke ses données.
Tâche 6 : Identifier la racine de données Docker (ne présumez pas /var/lib/docker)
cr0x@server:~$ docker info --format '{{.DockerRootDir}}'
/var/lib/docker
Signification : c’est là où Docker stocke images, couches et volumes nommés locaux (sauf si un driver change cela).
Décision : pour les volumes nommés, vous exporterez depuis ici ; pour les bind mounts, c’est surtout hors sujet sauf pour les stratégies de migration du moteur.
Tâche 7 : Trouver quels volumes sont utilisés vs orphelins (réduire la portée de migration)
cr0x@server:~$ docker volume ls
DRIVER VOLUME NAME
local billing_pgdata
local grafana-storage
local old_tmpdata
cr0x@server:~$ docker ps -a --format '{{.Names}}' | wc -l
7
Signification : la liste de volumes inclut des volumes orphelins. Le nombre de conteneurs vous indique combien de workloads pourraient les référencer.
Décision : identifiez les orphelins avant de migrer ; sinon vous transporterez fidèlement des octets morts comme un héritage familial que personne ne voulait.
Tâche 8 : Mapper un volume nommé sur les conteneurs qui l’utilisent
cr0x@server:~$ docker ps -a --filter volume=billing_pgdata --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
NAMES STATUS IMAGE
pg01 Up 3 hours postgres:16
Signification : seul pg01 utilise ce volume.
Décision : le plan de migration peut être par volume ; vous n’avez pas besoin d’exporter tout « au cas où ».
Tâche 9 : Exporter un volume nommé vers une archive tar (portable et ennuyeux)
cr0x@server:~$ docker run --rm -v billing_pgdata:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -cpf /backup/billing_pgdata.tar .'
cr0x@server:~$ ls -lh /backup/billing_pgdata.tar
-rw-r--r-- 1 root root 2.1G Jan 2 10:01 /backup/billing_pgdata.tar
Signification : vous avez un artefact unique représentant le contenu du volume.
Décision : c’est la valeur sûre par défaut pour les migrations de volumes nommés. Si la tar est énorme, envisagez temps d’arrêt vs stratégies incrémentales.
Tâche 10 : Importer une archive de volume nommé sur le nouvel hôte (et vérifier qu’elle n’est pas vide)
cr0x@server:~$ docker volume create billing_pgdata
billing_pgdata
cr0x@server:~$ docker run --rm -v billing_pgdata:/data -v /backup:/backup alpine:3.20 sh -c 'cd /data && tar -xpf /backup/billing_pgdata.tar'
cr0x@server:~$ docker run --rm -v billing_pgdata:/data alpine:3.20 sh -c 'ls -la /data | head'
total 72
drwx------ 19 999 999 4096 Jan 2 10:05 .
drwxr-xr-x 1 root root 4096 Jan 2 10:05 ..
drwx------ 5 999 999 4096 Jan 2 10:05 base
-rw------- 1 999 999 88 Jan 2 10:05 postmaster.opts
Signification : les fichiers existent et la propriété ressemble à celle typique de Postgres (UID 999 ici).
Décision : si la propriété est incorrecte, corrigez-la avant de démarrer Postgres, pas après qu’il ait échoué en plein milieu de la récupération.
Tâche 11 : Migrer un bind mount avec rsync (préserver propriété, xattrs, ACLs)
cr0x@server:~$ sudo rsync -aHAX --numeric-ids --delete /srv/api/config/ cr0x@newhost:/srv/api/config/
sending incremental file list
./
app.yaml
secrets.env
sent 24,198 bytes received 91 bytes 48,578.00 bytes/sec
total size is 18,422 speedup is 0.76
Signification : -aHAX conserve les métadonnées ; --numeric-ids évite les discordances de noms d’utilisateurs entre hôtes.
Décision : si rsync rapporte des suppressions massives de façon inattendue, arrêtez et confirmez que vous n’avez pas ciblé le mauvais chemin.
Tâche 12 : Vérifier les sommes de contrôle pour un répertoire bind-monté (intégrité, pas optimisme)
cr0x@server:~$ cd /srv/api/config && sudo find . -type f -maxdepth 2 -print0 | sort -z | xargs -0 sha256sum > /tmp/api-config.sha256
cr0x@server:~$ head /tmp/api-config.sha256
b2e58d0c5c2de0c8c61b49e0b8d8c0c95bdb9b5c5b718e3cc7c4f2c1f8f91ac4 ./app.yaml
c1aa9b3e0c6c4e99c7b2e5fd3d9b2a7a1bb6c32d08d5f9d7c7089a8d77c0e0a2 ./secrets.env
Signification : un manifeste de sommes de contrôle. Exécutez le même sur la destination et comparez.
Décision : pour la configuration/état critique, les comparaisons de sommes de contrôle battent le « ça semble ok » tous les jours.
Tâche 13 : Détecter les discordances UID/GID qui casseront les conteneurs après migration
cr0x@server:~$ stat -c '%n %u:%g %a' /srv/api/config
/srv/api/config 0:1002 750
cr0x@server:~$ getent group api
api:x:1002:
Signification : le répertoire appartient au groupe GID 1002. Si le nouvel hôte assigne à api un GID différent, l’accès de groupe casse.
Décision : standardisez l’allocation des UID/GID système (ou utilisez des IDs numériques de façon cohérente) pour les chemins bind-montés.
Tâche 14 : Vérifier le mode et le contexte SELinux (les migrations de bind mount adorent échouer ici)
cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ ls -Zd /srv/api/config
unconfined_u:object_r:default_t:s0 /srv/api/config
Signification : SELinux est en enforcing et le répertoire a default_t, ce qui bloque souvent l’accès au conteneur.
Décision : utilisez les bons labels (ou des options de montage dans la définition du conteneur). Migrer des données sans restaurer les contextes appropriés donne des « permission denied » avec des étapes supplémentaires.
Tâche 15 : Observer les symptômes de latence IO en direct (la « migration » est-elle réellement une régression de stockage ?)
cr0x@server:~$ iostat -x 1 3
Linux 6.8.0 (server) 01/02/2026 _x86_64_ (8 CPU)
avg-cpu: %user %nice %system %iowait %steal %idle
7.42 0.00 2.13 9.77 0.00 80.68
Device r/s w/s rMB/s wMB/s avgrq-sz avgqu-sz await r_await w_await svctm %util
nvme0n1 21.0 180.0 1.2 14.8 177.4 2.10 11.6 2.1 12.7 0.45 90.4
Signification : await ~11ms et %util ~90% suggèrent que le disque est occupé et que la latence n’est pas négligeable.
Décision : si l’hôte de destination montre une latence pire, votre migration « a réussi » mais votre base de données se plaindra fort. Corrigez la classe de stockage avant de blâmer Docker.
Tâche 16 : Confirmer quel type de montage est utilisé par un conteneur en cours d’exécution (vérification après basculement)
cr0x@server:~$ docker inspect api01 --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
bind /srv/api/config -> /app/config
Signification : post-migration, vous confirmez que le conteneur utilise le type de montage et le chemin prévus.
Décision : si vous attendiez un bind mount et voyez un volume, vous avez changé les sémantiques par accident. Arrêtez et corrigez avant d’accumuler un état divergent.
Playbook de diagnostic rapide
Vous avez migré. Quelque chose est lent, cassé, ou « marche sur un hôte mais pas sur l’autre ». Commencez ici. Cette séquence est conçue pour trouver rapidement le goulot d’étranglement,
pas pour satisfaire votre curiosité sur chaque couche.
1) Confirmez que vous montez bien ce que vous pensez monter
-
Exécutez
docker inspect <container>et vérifiez.MountspourType,SourceetDestination.
Un nombre surprenant d’incidents sont dus à « ça a monté un répertoire vide ». -
Pour les bind mounts, exécutez
findmnt -T /host/pathpour vous assurer qu’il est soutenu par le système de fichiers/appareil prévu.
2) Vérifiez permissions et labels de sécurité
-
Comparez la sortie de
statsur la source vs la destination. La dérive UID/GID est courante après des reconstructions. -
Si SELinux est en enforcing, vérifiez les contextes avec
ls -Z. Si AppArmor est en jeu, vérifiez la confinement du profil (souvent visible dans les logs du conteneur).
3) Vérifiez la santé du stockage avant d’optimiser l’application
-
Regardez la latence des dispositifs (
iostat -x) et la saturation du système de fichiers (df -h,df -i). - Si c’est une base de données et que vous voyez des timeouts, supposez la latence fsync jusqu’à preuve du contraire.
4) Ensuite seulement inspectez les couches spécifiques à Docker
-
Confirmez le répertoire racine Docker (
docker info), le driver de stockage, et si le mode rootless est utilisé. - Si vous avez copié le répertoire de données Docker entre hôtes, arrêtez-vous et vérifiez la compatibilité des versions du moteur et du driver de stockage avant d’aller plus loin.
Une idée paraphrasée souvent attribuée à Werner Vogels : « Tout échoue, tout le temps ; concevez et opérez comme si c’était vrai. »
Trois mini-histoires d’entreprise (comment les gens se font vraiment mal)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne faisait tourner un service de facturation avec Postgres dans Docker. Le fichier Compose utilisait un volume nommé : billing_pgdata.
Le modèle mental de l’équipe était : « volume nommé = persistant, persistant = survit au remplacement d’hôte. »
Personne n’a documenté où les octets vivaient. Personne n’en avait besoin. Jusqu’au jour où il en a eu besoin.
Une mise à jour OS de routine est devenue « reconstruisons l’instance ». L’équipe infra a terminé la VM ancienne après que la nouvelle a passé les checks de santé.
Les conteneurs sont remontés immédiatement. Postgres est remonté immédiatement aussi — sur un volume tout neuf créé sur le nouvel hôte. L’application est aussi montée et a commencé
à créer joyeusement de nouvelles lignes dans une base de données flambant neuve qui semblait parfaitement saine. Les alertes restaient vertes parce que la disponibilité était verte.
Le premier humain s’en est rendu compte quand la finance a demandé pourquoi les factures d’hier manquaient. À ce moment-là, les données locales de l’ancien hôte avaient disparu.
« Mais c’était un volume » s’est avéré être une affirmation sur l’API Docker, pas sur le cycle de vie de l’infrastructure.
La correction n’a pas été héroïque. Ils ont reconstruit depuis les sauvegardes (heureusement existantes) et ont écrit un runbook de migration traitant les volumes nommés
comme des artefacts exportables. Ils ont aussi ajouté une vérification canari : l’application refuse de démarrer si une empreinte du schéma n’est pas présente.
L’incident a enseigné une leçon sur les hypothèses. Les données n’avaient pas disparu. Leur compréhension oui.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre organisation voulait des fichiers Compose « plus propres » et moins de chemins hôtes. Ils ont remplacé les bind mounts par des volumes nommés partout,
y compris pour un service à forte écriture utilisant SQLite (oui, en production ; oui, ça arrive).
Leur raisonnement : les volumes nommés réduisent les erreurs de chemin et « Docker gère mieux ».
Les performances se sont effondrées après la migration — pas parce que les volumes nommés sont intrinsèquement lents, mais à cause de l’endroit où ils avaient atterri.
Le répertoire racine de Docker vivait sur une partition plus petite soutenue par un stockage réseau optimisé pour le débit, pas la latence.
Le pattern fsync de SQLite a puni ce stockage. L’app n’a pas planté ; elle est juste devenue plus lente d’une façon qui ressemblait à une saturation CPU et
à « peut-être avons-nous besoin de plus de pods », alors qu’aucun pod n’était impliqué.
L’équipe a chassé des fantômes : ils ont tune les PRAGMAs, ajouté des caches et envisagé de changer l’ORM. Le vrai problème était terne :
ils avaient déplacé l’IO d’état d’un disque local rapide (où vivait le bind mount) vers une couche de stockage à haute latence (où vivait /var/lib/docker).
Même octets. Physique différente.
Le rollback a été tout aussi terne : remettre la base sur un bind mount pointant vers le disque rapide, garder les volumes nommés pour les données moins sensibles,
et traiter le placement de la racine Docker comme une décision de capacité/performance de première classe. L’optimisation n’était pas fausse en concept.
Elle était fausse dans son contexte. Le contexte est l’endroit où vivent les incidents.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise globale faisait tourner plusieurs services internes sur des hôtes Docker fréquemment remplacés par une pipeline d’automatisation.
Rien de fancy. La partie ennuyeuse : chaque hôte avait une disposition de points de montage dédiée pour les données à état :
/srv/state/<service>, avec un système de fichiers par composant état majeur. Chaque système avait des snapshots et des politiques de sauvegarde.
Leurs conteneurs utilisaient exclusivement des bind mounts pour l’état. Les fichiers Compose n’étaient pas « portables » au sens où vous ne pouviez pas simplement les exécuter
sur votre laptop sans créer des répertoires. Mais l’environnement de production était cohérent, et la cohérence est essentiellement de la disponibilité en manteau.
Quand un hôte a subi une corruption de système de fichiers (ça arrive), la réponse a été presque ennuyeuse :
cordonner l’hôte, restaurer le système de fichiers affecté depuis le dernier snapshot, l’attacher à un hôte de remplacement, redémarrer les conteneurs.
Le rapport d’incident a été court parce que la conception du système supposait déjà le remplacement.
Leur arme secrète n’était pas un outil. C’était une pratique : chaque trimestre, ils répétaient la restauration de l’état d’un service sur un hôte neuf.
Pas parce qu’ils s’attendaient à en avoir besoin. Parce qu’ils s’attendaient à ne pas en avoir besoin, et ils ne faisaient pas confiance à ce sentiment.
Blague n°2 : La seule chose plus permanente qu’un conteneur Docker est la solution temporaire que vous avez utilisée pour migrer ses données.
Erreurs courantes : symptôme → cause racine → correction
1) « Ça a démarré, mais toutes les données ont disparu »
Symptôme : le service démarre avec une base vide ou une configuration par défaut après la migration.
Cause racine : le chemin du bind mount n’existait pas ou n’était pas monté ; Docker a créé un répertoire vide et l’a monté.
Avec les volumes nommés, vous avez créé un nouveau volume au lieu d’importer les anciennes données.
Correction : vérifiez les montages hôtes avec findmnt ; ajoutez des contrôles de démarrage (empreinte du schéma, fichiers attendus).
Pincez les noms de volumes dans Compose ; exportez/importez explicitement les volumes.
2) « Permission denied » à l’intérieur du conteneur après déplacement des données
Symptôme : les logs applicatifs montrent des échecs de lecture/écriture sur les chemins montés ; Postgres se plaint de la propriété du répertoire de données.
Cause racine : discordance UID/GID (surtout entre distributions), différences de user namespaces/mode rootless, ou ACLs/xattrs manquants.
Correction : migrez avec rsync -aHAX --numeric-ids ; standardisez UID/GID ; envisagez d’exécuter les conteneurs avec user: explicite.
Pour SELinux, appliquez les contextes corrects ou utilisez les options d’étiquetage de montage appropriées.
3) « Ça marche sur un hôte Ubuntu, échoue sur un hôte RHEL »
Symptôme : les chemins bind-montés sont illisibles ; les conteneurs ne peuvent pas accéder aux fichiers qui existent.
Cause racine : SELinux en enforcing avec des labels incorrects sur le répertoire bind-monté.
Correction : appliquez les labels SELinux corrects au répertoire ou utilisez les options de montage et politiques de conteneur appropriées.
Validez avec ls -Z avant de déclarer Docker « cassé ».
4) « La migration a réussi, mais les performances sont horribles »
Symptôme : latence accrue, timeouts, requêtes lentes, IOwait élevé.
Cause racine : les données du volume ont été déplacées vers une couche de stockage plus lente (souvent parce que la racine Docker est sur un disque différent qu’avant),
ou les options du système de fichiers diffèrent (barriers, mode journal, atime).
Correction : confirmez le dispositif et le système de fichiers avec findmnt, iostat -x, df.
Placez les bind mounts d’état sur le disque prévu. Si vous utilisez des volumes nommés, déplacez la racine Docker vers un système de fichiers adapté ou utilisez un driver de volume approprié.
5) « Nous avons copié /var/lib/docker et maintenant Docker ne démarre plus / les conteneurs sont corrompus »
Symptôme : erreurs du daemon Docker, couches manquantes, problèmes du système de fichiers overlay.
Cause racine : mismatch de version du moteur, mismatch du driver de stockage, incompatibilité de système de fichiers, ou copie partielle sans xattrs.
Correction : évitez de migrer l’ensemble du répertoire de données Docker comme stratégie à moins de l’avoir testée pour cet environnement exact.
Exportez/importez les volumes nommés et reconstruisez les images depuis les registries. Pour une récupération d’urgence, faites correspondre les versions et copiez en préservant les métadonnées.
6) « Les sauvegardes existent, mais la restauration ne fonctionne pas »
Symptôme : les sauvegardes restaurent des fichiers, mais le service ne démarre pas ou les données sont incohérentes.
Cause racine : vous avez sauvegardé un répertoire de base de données vivant sans snapshot cohérent applicatif ; ou vous avez restauré sans permissions/xattrs.
Correction : utilisez des sauvegardes natives à la base de données (ou des snapshots coordonnés). Pour les sauvegardes au niveau du système de fichiers, quiescez le service ou utilisez correctement les snapshots.
Listes de contrôle / plan étape par étape
Plan A : Si vous voulez que les migrations soient ennuyeuses (recommandé)
- Inventaire des montages : exécutez
docker psetdocker inspectpour lister les chemins et volumes stateful. - Classifiez les données : base de données, uploads utilisateurs, cache, artefacts de build, configuration, secrets. Seules certaines méritent la douleur.
- Choisissez la propriété du stockage :
- État critique : bind mount vers
/srv/state/<service>sur un système de fichiers dédié. - État remplaçable : les volumes nommés sont acceptables.
- État critique : bind mount vers
- Standardisez la disposition hôte : mêmes points de montage sur tous les hôtes, idéalement provisionnés par automatisation.
- Standardisez l’identité : assurez-vous que comptes de service et IDs numériques ne dériveront pas entre hôtes.
- Sauvegardez la bonne chose : les systèmes de fichiers bind-montés via votre système de sauvegarde normal ; les volumes nommés via des jobs d’export périodiques (tar vers stockage de sauvegarde).
- Répétez la restauration : une fois par trimestre, choisissez un service et restaurez-le sur un hôte neuf. Mesurez le temps jusqu’à l’utilisation.
- Ajoutez des garde-fous : l’application refuse de démarrer si la signature de données attendue est manquante ; alertez sur les événements « initialisation fraîche ».
Plan B : Si vous avez déjà utilisé des volumes nommés partout
- Énumérez les volumes nommés :
docker volume lset mappez-les aux conteneurs. - Exportez les volumes : tarez chaque volume nommé vers un chemin de sauvegarde avec une convention de nommage cohérente.
- Stockez avec intégrité : calculez les sommes de contrôle des tarballs et stockez-les à côté.
- Importez sur la destination : créez d’abord les volumes, puis extrayez les tarballs dedans.
- Vérifiez propriété et contenu : listez les répertoires clés à l’intérieur du volume via un conteneur temporaire.
- Basculez : démarrez les services, exécutez des tests smoke, validez la présence des données (pas seulement les endpoints de santé).
Plan C : Si vous avez besoin d’un « remplacement d’hôte le plus rapide possible »
C’est là que vous payez pour des décisions antérieures. Les remplacements rapides arrivent quand l’état vit sur un stockage pouvant être détaché/attaché ou répliqué
indépendamment du compute host.
- Mettez l’état sur une unité détachable : disque séparé, dataset, ou volume réseau.
- Bind-montez cette unité : les conteneurs montent des chemins
/srv/statequi pointent vers cette unité. - Automatisez l’attachement : l’hôte de remplacement boot, attache le stockage, le monte, puis démarre les conteneurs.
- Testez la défaillance : simulez la perte d’un hôte et mesurez le temps de récupération. Si vous ne le mesurez pas, vous ne l’avez pas.
FAQ
1) Les volumes nommés sont-ils « plus sûrs » que les bind mounts ?
Ils sont plus sûrs contre certains arbitres humains (mauvais chemin, montage accidentel de /), mais ils ne sont pas intrinsèquement plus sûrs pour la durabilité.
La durabilité vient du stockage sous-jacent et de votre discipline de sauvegarde/restauration.
2) Puis-je juste copier /var/lib/docker/volumes pour migrer des volumes nommés ?
Parfois. Assez de gens l’ont fait pour que ça paraisse normal. Mais c’est couplé à la version du moteur, au driver de stockage et aux détails du système de fichiers.
Si vous avez besoin d’une migration répétable, exportez/importez avec tar (ou utilisez un driver qui fournit la portabilité).
3) Pourquoi les bind mounts « survivent mieux aux migrations » s’ils sont liés aux chemins hôtes ?
Parce que le chemin est votre contrat, et vous pouvez l’appliquer avec de l’automatisation. Un bind mount n’est que des données normales sur un système de fichiers normal.
Cela fonctionne bien avec des outils de sauvegarde/réplication matures. Les volumes nommés sont aussi des fichiers normaux, mais cachés derrière la gestion et le placement de Docker.
4) Qu’en est-il de Docker Desktop sur macOS/Windows ?
Le moteur tourne à l’intérieur d’une VM. Les volumes nommés vivent dans cette VM. Les bind mounts traversent la frontière VM et ont leurs propres bizarreries de performance et de sémantique.
Pour les migrations, traitez les volumes Desktop comme « locaux à cette machine » sauf si vous les exportez délibérément.
5) Lequel est plus rapide : bind mounts ou volumes nommés ?
Sur Linux avec stockage local, les performances sont généralement similaires car les deux finissent en IO sur le système de fichiers. Les vraies différences sont :
où les données résident physiquement, les options de montage, et les couches de sécurité. Mesurez sur votre matériel ; ne vous fiez pas au folklore.
6) Les volumes nommés aident-ils avec les permissions ?
Ils peuvent, parce que Docker crée et possède souvent le répertoire sous sa racine et le maintient cohérent. Mais les permissions comptent toujours à l’intérieur du volume.
Si votre conteneur tourne en non-root, vous devez toujours avoir la propriété correcte.
7) Quelle est la meilleure pratique pour les bases de données dans Docker ?
Mettez la base sur un stockage que vous pouvez sauvegarder et restaurer de manière fiable. Dans beaucoup d’organisations cela signifie un bind mount vers un système de fichiers dédié ou un périphérique bloc géré,
plus des outils de sauvegarde applicatifs cohérents. Les volumes nommés sont acceptables si vos export/import et sauvegardes sont tout aussi disciplinés.
8) Comment éviter les désastres « répertoire vide monté » avec les bind mounts ?
Créez le répertoire et montez-le via l’automatisation. Ajoutez un contrôle pré-démarrage : si le répertoire ne contient pas un fichier marqueur attendu, échouez vite.
Et vérifiez les montages hôtes avec findmnt pendant le déploiement.
9) Si j’utilise Docker Compose, dois-je préférer les volumes nommés pour la portabilité ?
Préférez la portabilité seulement si elle ne sabote pas la récupérabilité. Pour le dev : les volumes nommés sont super. Pour l’état prod : utilisez des bind mounts vers des chemins hôtes stables,
ou acceptez les volumes nommés mais traitez export/import comme partie de votre cycle de déploiement.
10) Et les drivers de volume (NFS, cloud, etc.) ?
Ils peuvent résoudre la migration en mettant les données sur un stockage partagé ou détachable, mais ils ajoutent un nouveau domaine de défaillance : le driver et le service réseau/stockage.
Utilisez-les quand vous avez besoin d’état partagé ou de ré-hébergement rapide, et testez leur comportement en cas d’échec sous charge.
Prochaines étapes pratiques
- Faites un audit aujourd’hui : listez tous les montages et classez ceux qui sont état métier. Si vous ne pouvez pas le nommer, vous ne pouvez pas le protéger.
-
Choisissez une voie de migration et standardisez-la :
bind mounts sur une disposition cohérente/srv/state, ou volumes nommés avec export/import scriptés et vérification par sommes de contrôle. -
Déplacez intentionnellement la racine Docker : si les volumes nommés comptent pour vous, le système de fichiers sous
DockerRootDirest un stockage de production.
Traitez-le comme tel. - Rédigez le runbook « restaurer sous pression » : incluez les commandes exactes pour exporter/importer, corriger la propriété, valider la présence des données et vérifier les performances.
- Répétez : faites une restauration complète sur un hôte neuf. Chronométrez-la. Corrigez ce qui fait mal. Répétez jusqu’à ce que ce soit ennuyeux.
Les bind mounts ne sont pas magiques. Les volumes nommés ne sont pas mauvais. La différence tient à savoir si votre plan de migration dépend des internals de Docker ou des fondamentaux du système de fichiers.
Si vous voulez que les migrations deviennent routinières, construisez sur les fondamentaux.