Conteneurs Docker remplissent le disque : nettoyage de /tmp, des logs et des caches sans se brûler

Cet article vous a aidé ?

Le téléphone de l’astreinte vibre. L’API renvoie des 500. La latence est correcte, le CPU est en vacances, la mémoire exhibe son calme.
Puis vous voyez ça : No space left on device. Pas sur l’hôte que vous avez soigneusement dimensionné—à l’intérieur du système de fichiers du conteneur,
dans la pile de couches, dans les logs, dans le répertoire « temporaire » qui était censé être temporaire en 2019.

C’est la panne ennuyeuse la plus fréquente dans le monde des conteneurs. C’est aussi totalement évitable, si vous arrêtez de traiter le disque comme une
fosse sans fond et si vous commencez à considérer /tmp, les logs et les caches comme des éléments avec un budget.

Le modèle mental : où se situe réellement le « disque du conteneur »

Quand quelqu’un dit « le système de fichiers du conteneur est plein », ce qu’il veut généralement dire est : une couche inscriptible associée à un conteneur,
stockée sur l’hôte sous le driver de stockage de Docker (souvent overlay2), a épuisé l’espace sur le système de fichiers sous-jacent de l’hôte.
C’est le comportement par défaut pour les modifications du système de fichiers racine d’un conteneur. Ce n’est pas de la magie. C’est un arbre de répertoires et quelques montages.

Les conteneurs ne sont pas des machines virtuelles. Ils n’ont pas leur propre périphérique bloc à moins que vous ne leur en fournissiez un explicitement via des volumes, des mounts ou un CSI.
Si vous ne définissez pas de budgets de stockage, un conteneur peut joyeusement consommer le disque de l’hôte. Il est poli comme ça.

Faits intéressants et contexte historique (parce que les incidents ont des ancêtres)

  • Les systèmes de fichiers en union sont antérieurs à Docker : le modèle overlay est issu des longues tentatives du monde Linux pour rendre pratiques des bases immuables avec des sommets inscriptibles.
  • Les premiers drivers de stockage Docker formaient une ménagerie : aufs, devicemapper, btrfs, overlay, overlay2—chacun avec des modes de défaillance et des comportements de nettoyage différents.
  • Le logging json-file est devenu le « piège par défaut » : il était simple et fonctionnait partout, et il est devenu silencieusement la fuite disque de beaucoup.
  • La rotation des logs est plus vieille que les conteneurs : syslog et logrotate existent parce que le remplissage disque est un rite de passage Unix classique.
  • Les caches de build ont explosé avec le CI moderne : dès que les builds multi-étapes et le caching de couches sont devenus populaires, les caches de build sont devenus des résidents de production.
  • Les runtimes de conteneurs n’ont pas inventé le stockage éphémère : tmpfs existe depuis toujours ; on oublie juste de l’utiliser pour les données réellement temporaires.
  • Kubernetes a élevé le « stockage éphémère » au rang de ressource planifiable : principalement parce que suffisamment de nœuds sont morts à cause de la croissance des logs et des emptyDir.
  • La sémantique d’OverlayFS importe : supprimer un fichier à l’intérieur d’un conteneur ne « libère » pas toujours l’espace si quelque chose garde un handle ouvert (classique problème de fichier de log).

Une citation qui mérite d’être collée sur votre écran :
L’espoir n’est pas une stratégie. — couramment attribué dans les cercles ops ; idée paraphrasée de la pratique de fiabilité.
La gestion du disque n’est pas un endroit pour les sensations.

Blague #1 : Le disque est la seule ressource qui commence infinie puis devient soudainement zéro à 03:00.

Feuille de diagnostic rapide

C’est la séquence que j’utilise quand quelqu’un signale « conteneur disque plein » et s’attend à ce que je sois un magicien.
Ce n’est pas de la sorcellerie. C’est une checklist avec des opinions fortes.

Premier point : confirmer ce qui est réellement plein

  1. Système de fichiers de l’hôte ? Si la partition hôte qui supporte Docker est pleine, tout est en feu, même si un seul conteneur se fait entendre.
  2. Zone de stockage Docker ? /var/lib/docker (ou votre data-root configuré) se trouve souvent sur la mauvaise partition.
  3. Un volume ? Les volumes peuvent se remplir indépendamment et n’apparaîtront pas comme « usage de couche du conteneur ».
  4. Épuisement des inodes ? Vous pouvez avoir beaucoup d’octets libres et zéro inode. Les petits fichiers sont une excellente façon de ruiner votre weekend.

Deuxième point : trouver la catégorie de croissance

  1. Logs (logs Docker, logs applicatifs, logs d’accès, dumps de debug).
  2. Tmp (uploads, archives extraites, fichiers temporaires OCR, espace scratch pour traitement d’images).
  3. Caches (caches de paquets, caches de dépendances, caches de runtimes, caches navigateurs).
  4. Artefacts de build (restes de CI, cache de build, couches).
  5. Données écrites accidentellement dans la couche inscriptible du conteneur au lieu d’un volume.

Troisième point : choisir la soupape de secours la moins risquée

  1. Faire tourner / caper les logs (de préférence sans redémarrage).
  2. Supprimer les répertoires temporaires sûrs (avec règles claires de propriété et d’âge).
  3. Pruner les artefacts Docker inutilisés (avec ciblage prudent).
  4. Déplacer le chemin chaud vers un volume ou tmpfs (solution réelle).

Le goulot n’est pas le « disque ». Le goulot est l’absence d’un budget et d’un propriétaire.
Votre objectif : identifier le propriétaire et faire respecter le budget.

Tâches pratiques : commandes, sorties et décisions (12+)

Ce sont de vraies tâches que je fais sous pression. Chacune inclut : la commande, ce que signifie la sortie, et la décision suivante.
Lancez-les sur l’hôte sauf indication contraire.

Task 1: Is the host filesystem actually full?

cr0x@server:~$ df -hT
Filesystem     Type   Size  Used Avail Use% Mounted on
/dev/sda2      ext4   100G   96G  2.0G  98% /
tmpfs          tmpfs  3.1G  4.0M  3.1G   1% /run
/dev/sdb1      ext4   500G  120G  355G  26% /data

Signification : / est rempli à 98%. Si le data-root de Docker est sur /, vous êtes proche d’un arrêt complet.

Décision : Confirmer où Docker stocke les données. Si c’est sur /, prioriser un nettoyage immédiat et planifier une migration vers /data.

Task 2: Where is Docker storing its data?

cr0x@server:~$ docker info --format 'DockerRootDir={{.DockerRootDir}} Driver={{.Driver}} LoggingDriver={{.LoggingDriver}}'
DockerRootDir=/var/lib/docker Driver=overlay2 LoggingDriver=json-file

Signification : Le root de Docker est sur /var/lib/docker (sur /) utilisant overlay2. Les logs sont json-file.

Décision : S’attendre à ce que les répertoires overlay2 et les logs json soient les principaux suspects. Ne « prune »z pas tout aveuglément.

Task 3: Quick view of Docker’s disk usage breakdown

cr0x@server:~$ docker system df -v
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          24        12        18.4GB    6.2GB (33%)
Containers      36        18        4.8GB     1.1GB (22%)
Local Volumes   19        15        220.5GB   12.0GB (5%)
Build Cache     112       0         41.7GB    41.7GB

Signification : Les volumes dominent (220GB). Le build cache est important et entièrement récupérable. Les containers ajoutent ~5GB.

Décision : Si vous avez besoin d’un soulagement rapide, le build cache est une cible à faible risque. Mais auditez aussi les volumes—quelqu’un y stocke peut-être des données réelles.

Task 4: Find the biggest directories under /var/lib/docker

cr0x@server:~$ sudo du -xhd1 /var/lib/docker | sort -h
12G	/var/lib/docker/containers
41G	/var/lib/docker/buildkit
67G	/var/lib/docker/overlay2
220G	/var/lib/docker/volumes
340G	/var/lib/docker

Signification : Volumes et overlay2 sont lourds ; buildkit est substantiel.

Décision : Identifier quels conteneurs correspondent à ces volumes. Ne supprimez pas les données de volume sans connaître le propriétaire applicatif et la politique de rétention.

Task 5: Which containers are generating huge Docker logs?

cr0x@server:~$ sudo find /var/lib/docker/containers -name '*-json.log' -printf '%s %p\n' | sort -n | tail -5
187654321 /var/lib/docker/containers/4c9f.../4c9f...-json.log
322198765 /var/lib/docker/containers/8a21.../8a21...-json.log
988877766 /var/lib/docker/containers/1b77.../1b77...-json.log
2147483648 /var/lib/docker/containers/aa11.../aa11...-json.log
4123456789 /var/lib/docker/containers/f00d.../f00d...-json.log

Signification : Vous avez des json logs de plusieurs Go. C’est une pression disque avec un nom et une adresse.

Décision : Identifier les noms des conteneurs pour ces IDs, puis caper/faire tourner les logs. Ne supprimez pas les fichiers à l’aveugle si le démon écrit toujours.

Task 6: Map a container ID to a name and image

cr0x@server:~$ docker ps --no-trunc --format '{{.ID}} {{.Names}} {{.Image}}' | grep f00d
f00dbeefcafe1234567890abcdef1234567890abcdef1234567890abcdef api-prod myorg/api:2026.01

Signification : Le conteneur API génère de gros logs.

Décision : Traitez les logs comme un symptôme. Vous devez toujours corriger le niveau de log bruyant ou la boucle d’erreur.

Task 7: Check container writable layer size (smoke test)

cr0x@server:~$ docker ps -q | head -5 | xargs -I{} docker container inspect --format '{{.Name}} rw={{.SizeRw}} rootfs={{.SizeRootFs}}' {}
/api-prod rw=2147483648 rootfs=0
/worker-prod rw=536870912 rootfs=0
/nginx-prod rw=104857600 rootfs=0
/cron-prod rw=0 rootfs=0
/metrics rw=0 rootfs=0

Signification : SizeRw (couche inscriptible) pour api-prod est ~2GB. C’est généralement des logs, du tmp, ou des écritures accidentelles de données.

Décision : Exécutez un shell dans ce conteneur pour trouver où se situe l’espace, ou vérifiez le mappage des répertoires diff dans overlay2.

Task 8: Inside the container, find top disk consumers

cr0x@server:~$ docker exec -it api-prod sh -lc 'df -h; du -xhd1 / | sort -h | tail -10'
Filesystem                Size      Used Available Use% Mounted on
overlay                  100G       98G      2.0G  98% /
tmpfs                     64M         0       64M   0% /dev
tmpfs                    3.1G         0      3.1G   0% /sys/fs/cgroup
1.2G	/var
1.5G	/usr
2.8G	/tmp
6.0G	/app
9.1G	/log

Signification : /tmp et /log sont volumineux. C’est exploitable.

Décision : Déterminez si ces répertoires doivent être tmpfs, montés sur un volume, ou nettoyés de façon agressive.

Task 9: Find large individual files (often core dumps, trace dumps, or stuck uploads)

cr0x@server:~$ docker exec -it api-prod sh -lc 'find /tmp /log -type f -size +200M -printf "%s %p\n" | sort -n | tail -10'
2147483648 /log/app.log
536870912 /tmp/upload-20260103-0130.tmp
402653184 /tmp/image-cache.bin

Signification : Un seul log fait 2GB ; un fichier d’upload temporaire fait 512MB. Ce n’est pas subtil.

Décision : Caper les logs (rotation). Pour les uploads temporaires, garantir un nettoyage à la réussite/échec et envisager tmpfs ou un volume dédié avec quotas.

Task 10: Check if “deleted files” are still taking space due to open handles

cr0x@server:~$ sudo lsof +L1 | grep '/var/lib/docker/overlay2' | head
node    24781 root  21w  REG  8,2 2147483648  0 /var/lib/docker/overlay2/7a2.../diff/log/app.log (deleted)

Signification : Le fichier est supprimé mais toujours ouvert. L’espace ne sera pas libéré tant que le processus ne redémarrera pas ou ne ferme pas le descripteur de fichier.

Décision : Redémarrer le conteneur (ou recharger le processus) après s’être assuré qu’il reviendra proprement. Corriger aussi la rotation des logs pour que cela ne se répète pas.

Task 11: Prune build cache safely (usually low-risk)

cr0x@server:~$ docker builder prune --filter 'until=168h'
WARNING! This will remove all dangling build cache.
Are you sure you want to continue? [y/N] y
Deleted build cache objects:
r1m2n3...
freed: 38.6GB

Signification : Vous avez récupéré ~39GB sans toucher aux conteneurs en cours d’exécution.

Décision : Si c’était votre plus gros consommateur, planifiez ce prune sur les runners CI ou sur les nœuds builders avec une politique de rétention.

Task 12: Prune unused images (with eyes open)

cr0x@server:~$ docker image prune -a
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
deleted: sha256:1a2b...
Total reclaimed space: 5.9GB

Signification : De vieilles images prenaient de l’espace ; vous avez récupéré ~6GB.

Décision : Sur des nœuds prod, pruner les images peut ralentir les redeploys et casser les rollbacks si vous n’êtes pas prudent. Préférez exécuter cela sur le CI ou sur des nœuds avec une stratégie de déploiement contrôlée.

Task 13: Identify large volumes and map them to containers

cr0x@server:~$ docker volume ls -q | xargs -I{} sh -lc 'p=$(docker volume inspect -f "{{.Mountpoint}}" {}); s=$(sudo du -sh "$p" 2>/dev/null | awk "{print \$1}"); echo "$s {} $p"' | sort -h | tail -5
18G pgdata /var/lib/docker/volumes/pgdata/_data
22G redisdata /var/lib/docker/volumes/redisdata/_data
41G uploads /var/lib/docker/volumes/uploads/_data
55G elastic /var/lib/docker/volumes/elastic/_data
62G app-cache /var/lib/docker/volumes/app-cache/_data

Signification : Il y a un volume « app-cache » de 62GB. Ça peut être légitime, ou une croissance de cache sans limite.

Décision : Inspecter quels conteneurs le montent, puis définir éviction/rétention. Le cache n’est pas des données ; traitez-le comme jetable avec des limites.

Task 14: Which containers mount that volume?

cr0x@server:~$ docker ps --format '{{.Names}}' | xargs -I{} sh -lc 'docker inspect -f "{{.Name}} {{range .Mounts}}{{.Name}}:{{.Destination}} {{end}}" {}' | grep app-cache
/api-prod app-cache:/app/cache
/worker-prod app-cache:/app/cache

Signification : Deux services partagent le volume de cache. Les caches partagés sont pratiques et aussi excellents pour devenir des décharges.

Décision : Implémenter des limites de taille dans l’application et planifier un nettoyage. Si le cache est reconstruisable, envisager des caches par pod ou tmpfs.

Task 15: Check inode exhaustion (the “lots of tiny files” version of full disk)

cr0x@server:~$ df -hi
Filesystem    Inodes IUsed IFree IUse% Mounted on
/dev/sda2       6.4M  6.4M   12K  100% /

Signification : Vous avez épuisé les inodes, pas les octets. Causes courantes : logs non bornés, répertoires de cache avec des millions d’entrées, ou temp file storms.

Décision : Identifier le répertoire avec le plus de fichiers et le nettoyer. Envisager le tuning du filesystem ou déplacer DockerRootDir vers un filesystem avec plus d’inodes.

Task 16: Find directories with crazy file counts

cr0x@server:~$ sudo sh -lc 'for d in /var/lib/docker /var/log /tmp; do echo "## $d"; find "$d" -xdev -type f 2>/dev/null | wc -l; done'
## /var/lib/docker
1892345
## /var/log
45678
## /tmp
1203

Signification : La plupart des fichiers sont sous la zone Docker. Overlay2 et les logs des conteneurs peuvent exploser l’utilisation des inodes.

Décision : Raffiner l’analyse dans /var/lib/docker, en commençant par overlay2 et containers. Si c’est le build cache, pruner.

/tmp et fichiers temporaires : stratégies de nettoyage qui n’effacent pas la mauvaise chose

L’espace temporaire est l’endroit où les bonnes intentions fermentent. Dans les conteneurs, /tmp est souvent sur la couche inscriptible, ce qui signifie :
il concurrence tout le reste pour le même disque hôte et il survit plus longtemps que vous ne le pensez (les redémarrages de conteneur ne le nettoient pas forcément).

Stratégie : décider ce que « temporaire » signifie pour votre charge

  • Vraiment éphémère (traitement d’images, unzip, tri, petits buffers) : utiliser tmpfs avec une taille limitée.
  • Temporaire volumineux pour uploads (multi-GB) : utiliser un volume dédié ou un chemin hôte avec quotas ; ne supposez pas que tmpfs vous sauvera.
  • Queues de travaux et spill files : les rendre explicites et observables ; définir TTL et taille max.

Monter /tmp en tmpfs (bon choix par défaut pour beaucoup d’apps web)

Dans docker run :

cr0x@server:~$ docker run --rm -it --tmpfs /tmp:rw,noexec,nosuid,size=256m alpine sh -lc 'df -h /tmp'
Filesystem                Size      Used Available Use% Mounted on
tmpfs                   256.0M         0    256.0M   0% /tmp

Signification : /tmp est maintenant en RAM et limité à 256MB.

Décision : Si votre app utilise /tmp pour de petits fichiers scratch, cela empêche les fuites disque et rend les échecs bruyants (comportement de type ENOMEM) plutôt que de remplir silencieusement l’hôte.

Pour Compose :

cr0x@server:~$ cat docker-compose.yml
services:
  api:
    image: myorg/api:2026.01
    tmpfs:
      - /tmp:rw,noexec,nosuid,size=256m

Règle de nettoyage : ne faites pas « rm -rf /tmp » en production comme un méchant de dessin animé

L’approche sûre est la suppression basée sur l’âge dans un répertoire possédé par votre app, pas une suppression globale.
Faites en sorte que votre app écrive dans /tmp/myapp, puis nettoyez seulement ce sous-arbre.

cr0x@server:~$ docker exec -it api-prod sh -lc 'mkdir -p /tmp/myapp; find /tmp/myapp -type f -mmin +120 -delete; echo "cleanup done"; du -sh /tmp/myapp'
cleanup done
12M	/tmp/myapp

Signification : Les fichiers vieux de plus de deux heures ont été supprimés ; le répertoire fait maintenant 12MB.

Décision : Si cela aide de façon répétée, votre app fuit des fichiers temporaires sur les chemins d’erreur. Corrigez le code, mais gardez le balai.

Blague #2 : Rien n’est plus permanent qu’un répertoire temporaire sans propriétaire.

Logs : json-file Docker, journald et logs applicatifs

Les logs sont la raison numéro 1 pour laquelle « les conteneurs ont rempli le disque » apparaît dans les timelines d’incident.
Pas parce que le logging est mauvais—parce que logger sans limites est une attaque par déni de service en mode lent contre votre propre disque.

json-file Docker : maîtrisez-le ou il vous noiera

Le logging Docker par défaut (json-file) écrit des logs par conteneur sous /var/lib/docker/containers/<id>/<id>-json.log.
Si vous ne définissez pas de rotation, ils grandissent indéfiniment. Indéfiniment est plus long que votre disque.

Définir la rotation des logs dans /etc/docker/daemon.json

cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "5"
  }
}

Signification : Le log géré par Docker de chaque conteneur est limité à ~250MB (5 fichiers × 50MB).

Décision : Appliquer cela sur chaque hôte. Puis redémarrer Docker dans une fenêtre de maintenance. Si vous ne pouvez pas redémarrer, planifiez un déploiement progressif ; ne l’ignorez pas.

Et journald ?

Si Docker logue vers journald, la croissance se déplace vers le journal système. Bonne nouvelle : journald supporte des limites de taille centralisées.
Mauvaise nouvelle : votre disque se remplit toujours, juste avec des fichiers différemment calibrés.

cr0x@server:~$ sudo journalctl --disk-usage
Archived and active journals take up 7.8G in the file system.

Signification : Les journaux occupent 7.8GB. Pas catastrophique, mais pas gratuit.

Décision : Définir la rétention (SystemMaxUse, SystemMaxFileSize) si cela augmente progressivement.

Logs applicatifs à l’intérieur du conteneur : décider où ils doivent aller

En général, vous voulez que les apps loguent vers stdout/stderr et laisser la plateforme gérer l’acheminement des logs. Quand les apps écrivent dans /log
à l’intérieur du système de fichiers du conteneur, vous avez créé un second système de logs avec des règles de rétention différentes et une tendance plus forte à remplir les disques silencieusement.

Si vous devez écrire des fichiers (conformité, batch, apps legacy), mettez-les sur un volume avec rétention et rotation explicites.
Et testez la rotation : la rotation des logs est un changement qui peut casser des choses.

Tronquer en toute sécurité quand le fichier est trop gros (seulement en urgence)

cr0x@server:~$ sudo sh -lc ': > /var/lib/docker/containers/f00dbeefcafe*/f00dbeefcafe*-json.log; ls -lh /var/lib/docker/containers/f00dbeefcafe*/f00dbeefcafe*-json.log'
-rw-r----- 1 root root 0 Jan  3 02:11 /var/lib/docker/containers/f00dbeefcafe123.../f00dbeefcafe123...-json.log

Signification : Vous avez tronqué le fichier en place. Docker peut continuer à écrire sans avoir besoin de rouvrir un nouvel inode.

Décision : Ne faites cela qu’en solution d’atténuation lors d’un incident. La vraie correction est la rotation des logs + réduire le volume de logs à la source.

Caches : gestionnaires de paquets, runtimes et artefacts de build

Le cache est une fonctionnalité de performance qui se transforme en bug de stockage quand personne ne s’en occupe. Les conteneurs amplifient cela parce que les mêmes patterns de cache qui vont sur un portable dev
deviennent une croissance sans limite sur un nœud avec des dizaines de services.

Points chauds de cache courants

  • Gestionnaires de paquets OS : caches apt, apk, yum laissés dans les images ou créés à l’exécution.
  • Runtimes : pip cache, npm cache, Maven/Gradle, Ruby gems, Go build cache.
  • Caches de navigateurs headless : les répertoires de cache de Chromium peuvent gonfler.
  • Caches applicatifs : caches de miniatures, templates compilés, caches de requêtes, exportations « temporaires ».

Dans un conteneur en cours : trouver les répertoires de cache

cr0x@server:~$ docker exec -it worker-prod sh -lc 'du -xhd1 /root /var/cache /app | sort -h | tail -15'
12M	/var/cache
420M	/root
2.3G	/app

Signification : Quelque chose sous /app est volumineux ; le home de root fait 420MB (souvent des caches runtime).

Décision : Inspecter le sous-arbre /app pour des répertoires de cache et décider : doit-il être un volume, doit-il avoir une limite de taille, ou doit-il être supprimé au démarrage ?

Empêcher les caches d’atterrir dans la couche inscriptible

Si le cache est jetable et local, montez un volume dédié au cache. Ainsi il ne fait pas gonfler la couche du conteneur et il est plus facile à nettoyer.
Si vous avez besoin de limites strictes, envisagez des quotas de filesystem sur ce chemin de volume côté hôte (ou utilisez des storage classes dans Kubernetes).

Aussi : ne construisez pas d’images qui emportent des caches. Si vous utilisez des Dockerfiles, nettoyez les caches de paquets dans la même couche où vous installez les paquets,
sinon la couche contient toujours l’ancienne donnée. Ce n’est pas un jugement moral ; c’est la façon dont fonctionnent les fichiers système en couches.

Artefacts Docker : images, couches, overlay2, cache de build

Docker crée et retient beaucoup de choses : images, couches, containers arrêtés, réseaux, volumes, cache de build.
Les règles de rétention sont intentionnellement conservatrices parce que supprimer la mauvaise chose casse des workflows. Votre travail est d’ajuster ces règles pour votre environnement.

Croissance d’overlay2 : le coût caché d’écrire dans root

Overlay2 stocke les modifications inscriptibles par conteneur dans des répertoires sous /var/lib/docker/overlay2.
Quand une application écrit dans /var/lib/postgresql ou /uploads sans volume, elle écrit dans overlay2.
Ça marche jusqu’au jour où le nœud meurt de succès.

Trouver quels conteneurs ont de grosses couches inscriptibles

cr0x@server:~$ docker ps -q | xargs -I{} docker inspect --format '{{printf "%-25s %-12s\n" .Name .SizeRw}}' {} | sort -k2 -n | tail
/api-prod                 2147483648
/worker-prod              536870912
/nginx-prod               104857600

Signification : Ces conteneurs écrivent des données significatives dans la couche inscriptible.

Décision : Pour chacun, décider si les écritures doivent aller sur stdout, tmpfs, ou un volume nommé. « Garder dans le conteneur » n’est pas une stratégie de stockage.

Pruning : utile, dangereux, et parfois les deux

docker system prune est la tronçonneuse. Pratique quand la forêt est en feu, mais vous pouvez aussi amputer votre capacité à rollbacker ou rebuild rapidement.
En production, préférez des prunes ciblés :

  • Prune du build cache sur les nœuds builders et runners CI.
  • Prune d’images avec conscience de la stratégie de déploiement et des besoins de rollback.
  • Prune de containers uniquement si vous accumulez des containers arrêtés sans raison.
  • Prune de volumes seulement quand vous êtes sûr de ne pas supprimer des données en production.
cr0x@server:~$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
3d2c...
Total reclaimed space: 1.0GB

Signification : Des containers arrêtés consommaient de l’espace.

Décision : Si vous voyez régulièrement beaucoup de containers arrêtés, corrigez votre processus de déploiement ou vos crash loops. Le nettoyage n’est pas la correction de la cause racine.

Volumes et bind mounts : le disque « pas réellement dans le conteneur »

Les volumes sont l’endroit où les données durables doivent vivre. Ils sont aussi l’endroit où les caches deviennent « accidentellement durables ».
Le mode de défaillance est prévisible : l’application agrandit silencieusement un répertoire, le disque du nœud se remplit, et tout le monde blâme Docker.

Auditer les volumes comme vous auditez les bases de données

L’usage des volumes est opérationnellement significatif. Mettez des alertes dessus. Mettez des propriétaires dessus. Si c’est un cache, mettez de l’éviction et une taille maximale.
Si ce sont des données durables, mettez des backups et des politiques de rétention.

Bind mounts : pratiques et tranchants

Les bind mounts peuvent contourner le stockage géré par Docker et écrire directement sur le filesystem hôte. Ça peut être bien (séparer les données de DockerRootDir)
ou terrible (écrire dans / sur une petite partition root).

cr0x@server:~$ docker inspect api-prod --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
volume /var/lib/docker/volumes/app-cache/_data -> /app/cache
bind /data/uploads -> /uploads

Signification : Uploads sont bind-montés sur /data/uploads. Super—si /data est volumineux et monitoré.

Décision : Confirmer que le chemin hôte a des quotas/alertes. Les bind mounts écrivent effectivement sur l’hôte, alors traitez-les comme tels.

Notes Kubernetes : stockage éphémère et DiskPressure

Dans Kubernetes, les problèmes disque se manifestent généralement par DiskPressure, des évictions de pods, et des nœuds passant en NotReady.
Les causes sous-jacentes sont les mêmes : logs, emptyDir, couches inscriptibles des conteneurs, cache d’images, et volumes.
Kubernetes ajoute juste un ordonnancement et un mode d’échec plus bruyant.

Point opérationnel clé : request et limit pour le stockage éphémère

Si vous ne définissez pas de requests/limits pour le stockage éphémère, le kubelet ne peut pas prendre de décisions d’ordonnancement intelligentes et finira par évincer des pods quand le nœud est déjà mal en point.
Ce n’est pas de la planification de capacité ; c’est de la gestion en panique.

emptyDir n’est pas une poubelle sans couvercle

emptyDir sans sizeLimit (et sans monitoring) est un chèque en blanc.
Si la charge a besoin d’espace scratch, définissez un budget. Si elle a besoin de stockage durable, utilisez un PVC.

cr0x@server:~$ kubectl describe node worker-3 | sed -n '/Conditions:/,/Addresses:/p'
Conditions:
  Type             Status  LastHeartbeatTime                 Reason              Message
  DiskPressure     True    Fri, 03 Jan 2026 02:19:54 +0000   KubeletHasDiskPressure   kubelet has disk pressure

Signification : Le nœud subit une pression disque et commencera à évincer.

Décision : Identifier immédiatement les plus gros consommateurs (logs de conteneurs, images, emptyDir, couches inscriptibles) et soulager la pression. Puis ajouter des limites et des alertes.

Trois mini-histoires d’entreprise (réalistes, douloureuses, utiles)

1) Incident causé par une mauvaise hypothèse : « Les conteneurs sont éphémères, donc ils ne peuvent pas remplir le disque »

Une entreprise de taille moyenne exécutait une API liée aux paiements sur quelques hôtes Docker. L’application était stateless, donc tout le monde a supposé que le stockage « n’était pas un sujet ».
Les déploiements étaient un peu blue/green : de nouveaux conteneurs montaient, les anciens s’arrêtaient, et personne ne regardait les conteneurs arrêtés parce qu’« ils ne tournent pas ».

Sur quelques mois, le filesystem root des hôtes est monté discrètement de confortable à précaire. Le vrai coupable était ennuyeux :
logs json Docker sur quelques conteneurs bavards, plus des dizaines de conteneurs arrêtés conservés pour « debugging plus tard ».
Les logs n’étaient jamais tournés. Les containers arrêtés avaient des couches inscriptibles pleines de fichiers temporaires créés pendant des pics de requêtes.

L’incident est survenu pendant un déploiement de routine. Docker a essayé de pull une nouvelle couche d’image. Le pull a échoué avec no space left on device.
La logique d’orchestration a continué d’essayer, créant plus de téléchargements partiels et plus de logs d’erreur. Maintenant le disque n’était pas seulement plein ; il était attaqué par les retries.

Le rétablissement a été la comédie favorite d’un incident commander : la correction la plus rapide a été d’arrêter la boucle de retry et de pruner le build cache et de vieilles images.
Mais l’équipe a d’abord essayé de supprimer des fichiers de logs à l’intérieur des conteneurs, ne réalisant pas que Docker écrivait ailleurs. Ils n’ont récupéré presque rien et ont perdu du temps.
Une fois qu’ils ont tourné les logs Docker et pruné les containers arrêtés, le nœud s’est rétabli.

La cause racine n’était pas « Docker ». C’était l’hypothèse que le compute éphémère implique un stockage éphémère.
Les conteneurs sont éphémères ; les fichiers qu’ils créent sur l’hôte sont très engagés dans la relation.

2) Optimisation qui s’est retournée contre eux : « Cachez tout sur un volume partagé »

Une autre organisation traitait des requêtes coûteuses : génération de PDF plus rendu d’images. Quelqu’un a proposé une idée sensée :
mettre en cache les assets intermédiaires et les sorties rendues pour accélérer les requêtes répétées. Ils l’ont implémenté comme un volume Docker partagé monté sur /app/cache
à la fois par les conteneurs API et worker.

Les performances se sont améliorées immédiatement. La latence a baissé, le CPU a chuté, et l’équipe s’est félicitée d’avoir « scalé sans scaler ».
Le répertoire de cache est devenu l’histoire à succès dans la revue métrique hebdomadaire. Personne n’a demandé jusqu’où il pouvait grandir ni comment il serait nettoyé.

Un mois plus tard, le nœud a commencé à flapper. Pas le CPU, pas la mémoire—le disque. Le volume de cache avait grandi régulièrement puis brutalement,
parce qu’une nouvelle fonctionnalité a augmenté la variété des clés de cache et réduit le hit rate. Le cache n’agissait plus comme un cache ; il agissait comme une seconde base de données,
sauf sans compactage, sans TTL, et sans backups.

Le mode de défaillance était sournois : une fois le volume plein, les écritures échouaient. L’app a interprété les échecs d’écriture comme des « cache miss »,
donc elle recomputait le travail coûteux. La charge a augmenté. Le système a ralenti, les erreurs ont augmenté, et le cache s’est mis à thrash.
C’était une optimisation de performance qui s’est transformée en bug de fiabilité.

La correction n’a pas été héroïque : appliquer une taille maximale au cache, évincer par LRU/âge, et traiter le volume de cache comme une ressource avec un budget.
Ils ont aussi rendu le cache par worker dans certains cas pour éviter un hotspot partagé. Les performances sont restées correctes, et le disque a cessé d’être le tueur silencieux.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : caps stricts de logs et revue hebdomadaire du budget d’artefacts

Une grande équipe plateforme interne gérait des centaines de conteneurs par cluster. Ils étaient allergiques aux incidents disque parce que ces incidents sont bruyants, politiques,
et surviennent généralement quand la direction regarde une démo.

Leur approche était agressivement terne. Chaque nœud avait la rotation des logs Docker configurée par défaut. Chaque charge avait une politique documentée :
si vous logguez sur stdout, vous obtenez de la centralisation ; si vous logguez sur fichiers, vous devez tourner et stocker sur un volume dédié.
Ils avaient une revue hebdomadaire du « budget d’artefacts » : top volumes, top images, top caches de build, et top producteurs de logs. Pas une séance de blâme—juste un coup de balai d’hygiène.

Pendant un incident, un service est entré dans une boucle d’erreur serrée et a commencé à émettre des stack traces verbeux. Dans un autre environnement, cela aurait rempli les disques
et arrêté des services non liés. Ici, le cap de log Docker a contenu les dégâts. L’observabilité n’a pas disparu : les logs ont continué de circuler,
simplement avec rotation et rétention bornée au niveau du nœud.

L’équipe a quand même dû corriger le bug. Mais elle n’a pas eu à récupérer une flotte à partir d’un épuisement disque pendant qu’elle corrigeait.
C’est le vrai bénéfice des garde-fous ennuyeux : ils vous donnent du temps pour faire le vrai travail.

Erreurs fréquentes (symptôme → cause racine → correction)

1) « Le disque est plein mais j’ai supprimé des fichiers et rien n’a changé »

Symptôme : Vous supprimez de gros fichiers de logs, mais df montre toujours peu d’espace libre.

Cause racine : Le processus garde le fichier ouvert (inode supprimé mais ouvert). Classique avec les fichiers de logs.

Correction : Utilisez lsof +L1 pour trouver les fichiers supprimés encore ouverts, puis redémarrez le conteneur/processus ou faites une rotation correcte des logs.

2) « J’ai fait docker system prune et le disque s’est rempli à nouveau un jour après »

Symptôme : Soulagement temporaire suivi d’un épuisement récurrent du disque.

Cause racine : Vous avez traité les symptômes (artefacts) alors que la charge continue de générer des logs/tmp/caches sans limite.

Correction : Ajouter des caps de rotation de logs ; monter tmp en tmpfs avec une taille ; implémenter l’éviction de cache ; déplacer les écritures de données vers des volumes avec rétention.

3) « Le conteneur dit disque plein mais l’hôte a de l’espace »

Symptôme : L’hôte /data a des centaines de GB libres, mais la couche overlay du conteneur montre 98% utilisé.

Cause racine : DockerRootDir est sur / (petit), tandis que d’autres partitions sont grandes.

Correction : Déplacer DockerRootDir vers le filesystem plus grand (migration planifiée). À court terme : pruner build cache/images et caper les logs.

4) « Nous avons défini la rotation des logs mais les logs explosent toujours »

Symptôme : Les json logs continuent de croître au-delà de l’attendu.

Cause racine : La config de rotation s’applique seulement aux nouveaux conteneurs ; ou un driver de logging différent est utilisé ; ou l’app écrit dans des fichiers.

Correction : Vérifier docker info le logging driver et les options de logging par conteneur. Redeployez les conteneurs après modification de la config du démon.

5) « Les inodes atteignent 100% avec beaucoup de GB libres »

Symptôme : df -hi montre 100% d’utilisation des inodes.

Cause racine : Millions de petits fichiers : répertoires de cache, archives extraites, création incontrôlée de fichiers temporaires.

Correction : Identifier les hotspots par nombre de fichiers ; nettoyer de façon agressive ; repenser le stockage du cache (moins de fichiers) ou utiliser un filesystem avec plus d’inodes.

6) « Le volume continue de croître mais c’est « juste du cache » »

Symptôme : Un volume de cache grandit sans borne.

Cause racine : Le cache n’a pas de TTL/éviction ; les clés deviennent effectivement uniques ; la charge a changé.

Correction : Implémenter éviction et taille maximale dans la logique applicative ; envisager des caches par instance ; ajouter des alertes et tableaux de bord pour le volume.

7) « Le job de nettoyage a supprimé les mauvais fichiers »

Symptôme : L’application échoue après un cron de nettoyage.

Cause racine : Le nettoyage a fait des suppressions larges (p.ex. /tmp ou /var) sans chemins propres à l’app et sans règles d’âge.

Correction : Contraindre le nettoyage aux chemins possédés par l’app ; utiliser la suppression basée sur l’âge ; écrire dans des répertoires dédiés ; tester en staging avec des charges réalistes.

Checklists / plan étape par étape

Étape par étape : soulagement d’urgence (garder les services vivants)

  1. Confirmer ce qui est plein : df -hT et df -hi. Décider s’il s’agit d’octets ou d’inodes.
  2. Vérifier la répartition Docker : docker system df -v pour voir si ce sont les volumes, le build cache, les images ou les containers.
  3. Arrêter l’hémorragie : si les logs sont énormes, tronquer en place ou redémarrer le coupable après avoir capturé assez de diagnostics.
  4. Récupérer l’espace à faible risque : pruner le build cache ; pruner les containers arrêtés ; pruner les images inutilisées si votre stratégie de déploiement le permet.
  5. Valider le rétablissement : vérifier l’espace libre et que les services ne sont pas dans des boucles de retry créant plus d’occupation disque.

Étape par étape : correction durable (rendre ça ennuyeux)

  1. Définir des caps de logs Docker globalement (json-file max-size/max-file) et redéployer les conteneurs.
  2. Déplacer DockerRootDir hors des petites partitions root. Mettre sur un stockage dédié dimensionné pour les artefacts et les logs.
  3. Arrêter d’écrire des données durables dans les couches inscriptibles : imposer des volumes pour tout ce qui est persistant.
  4. Utiliser tmpfs pour le vrai temporaire : monter /tmp en tmpfs avec des caps de taille quand approprié.
  5. Placer la politique de cache dans le code : TTL + taille max + éviction. « On nettoiera plus tard » n’est pas une politique.
  6. Ajouter des alertes : utilisation du filesystem hôte, usage de DockerRootDir, usage par volume, usage d’inodes, et anomalies du taux de logs.
  7. Opérationnaliser le nettoyage : prunes programmés du build cache sur les nodes CI/builder, avec fenêtres de rétention et contrôle des changements.

Ce qu’il faut éviter (parce que vous serez tenté)

  • Évitez de supprimer des répertoires aléatoires sous /var/lib/docker pendant que Docker tourne. C’est une excellente façon de corrompre l’état.
  • Évitez volume prune en production à moins d’avoir la confirmation explicite que les volumes sont inutilisés.
  • Évitez « rm -rf /tmp/* » si plusieurs processus partagent /tmp. Possédez vos répertoires temporaires.
  • Évitez de traiter le prune comme de la maintenance pour un système qui génère activement des écritures non bornées.

FAQ

1) Pourquoi supprimer un fichier de log ne libère-t-il pas d’espace ?

Parce qu’un processus peut continuer à écrire sur un descripteur de fichier ouvert même après la suppression du chemin. L’espace disque n’est libéré que lorsque le handle se ferme.
Utilisez lsof +L1 et redémarrez/rechargez le processus.

2) Dois-je passer Docker de json-file à journald ?

Journald peut centraliser la rétention et s’intégrer aux outils système, mais ce n’est pas une victoire automatique. Si vous choisissez journald, définissez des limites de taille pour journald.
Si vous gardez json-file, définissez max-size et max-file. Choisissez une option et gérez-la sérieusement.

3) Est-ce que docker system prune est sûr en production ?

« Sûr » dépend de vos attentes de rollout/rollback. Il peut supprimer des images inutilisées et du build cache, ce qui peut ralentir les redeploys ou supprimer des images de rollback.
Préférez des prunes ciblés (cache builder, containers arrêtés) et faites un pruning agressif sur les nœuds CI, pas sur les nœuds prod critiques.

4) Quelle est la meilleure façon d’empêcher /tmp de se remplir ?

Si l’usage temporaire est petit et vraiment éphémère, montez /tmp en tmpfs avec une limite de taille. Si l’usage temporaire peut être volumineux, utilisez un volume dédié avec monitoring et nettoyage.
Dans tous les cas, faites écrire l’app dans un sous-répertoire possédé et nettoyez par âge.

5) Comment savoir si la couche inscriptible du conteneur est le problème ?

Regardez docker inspect SizeRw pour les conteneurs, et à l’intérieur du conteneur exécutez du -xhd1 /.
Si de gros répertoires se trouvent à des emplacements qui devraient être des volumes ou du tmpfs, vous avez trouvé le problème.

6) Pourquoi les caches de build deviennent-ils si gros ?

Les builds modernes génèrent beaucoup de couches intermédiaires et d’objets de cache. BuildKit est rapide parce qu’il stocke le travail ; il est aussi vorace.
Prunez le build cache selon un planning, surtout sur les builders partagés et les runners CI.

7) Et l’épuisement d’inodes—comment le prévenir ?

Empêchez la création de millions de petits fichiers (designs de cache qui shardent en trop de fichiers, temp storms).
Surveillez l’utilisation des inodes avec df -hi. Mettez les caches à fort nombre de fichiers sur des filesystems conçus pour ça ou repensez la disposition du cache.

8) Puis-je appliquer des limites disque par conteneur dans Docker « pur » ?

Docker n’offre pas une limite disque universelle simple comme pour le CPU/mémoire. Vous pouvez approcher cela avec des limites tmpfs, des volumes sur des filesystems à quotas,
et des garde-fous opérationnels (caps de logs, limites de cache). Dans Kubernetes, utilisez requests/limits pour le stockage éphémère.

9) Les logs applicatifs doivent-ils aller sur stdout ou dans des fichiers ?

Stdout/stderr est généralement la bonne approche pour les conteneurs : collecte centralisée, rotation gérée par la plateforme, moins de pièces mobiles.
Les logs fichiers sont acceptables pour des besoins spécifiques, mais alors vous assumez la rotation, la rétention et le budget disque explicitement.

Prochaines étapes que vous pouvez livrer cette semaine

  1. Définir la rotation des logs Docker globalement (json-file max-size/max-file) et redéployer d’abord les services les plus bruyants.
  2. Instrumenter des alertes disque et inode pour le filesystem hébergeant DockerRootDir et pour vos principaux volumes.
  3. Identifier les 3 principaux consommateurs disque en utilisant docker system df -v et du sous DockerRootDir, puis assigner des propriétaires.
  4. Convertir le « temporaire » en tmpfs là où c’est sûr avec des caps de taille explicites. Rendre l’échec bruyant, pas contagieux.
  5. Mettre l’éviction du cache dans le code et prouver que cela fonctionne sous charge. Si vous ne pouvez pas expliquer la taille maximale de votre cache, ce n’est pas un cache.
  6. Planifier le pruning du build cache sur les nœuds builder/CI avec une fenêtre de rétention qui correspond à votre cadence de développement.

L’objectif n’est pas des scripts de nettoyage héroïques. L’objectif est un comportement de stockage prévisible. Les conteneurs se créent vite et se tuent facilement.
Votre disque ne l’est ni l’un ni l’autre.

← Précédent
ZFS dRAID : Resilver plus rapide — ou simplement une complexité accélérée ?
Suivant →
IP/Domaine d’e-mail sur liste noire : comment vérifier et se faire retirer correctement

Laisser un commentaire