Images Docker : les pièges qui gonflent votre facture
Un registre Docker ne facture pas uniquement les images utilisées en production. Il conserve aussi les versions intermédiaires, les balises de test, les doublons de dépendances et les artefacts issus de compilations mal conçues.

Images Docker: les pièges qui gonflent votre facture
Sur Amazon ECR, le stockage privé est facturé environ 0,10 dollar par Go et par mois. Le montant unitaire paraît faible. La dérive vient du volume accumulé et des téléchargements répétés hors réseau local.
Le problème est rarement une seule image exceptionnellement volumineuse. Il s’agit d’une chaîne de décisions faibles: une image de développement utilisée comme image finale, un fichier temporaire conservé dans une couche, une balise créée à chaque compilation, aucune règle de rétention et des déploiements qui tirent les mêmes données depuis plusieurs régions.
Réduire le coût de stockage d’un registre Docker exige donc deux actions distinctes: alléger les images produites et supprimer automatiquement celles qui n’ont plus de fonction opérationnelle. Optimiser une seule de ces dimensions laisse une partie de la facture intacte.
La mécanique cachée du surcoût: pourquoi vos images pèsent trop lourd
Docker ne stocke pas une application comme un fichier monolithique. Une image est composée de couches. Chaque instruction du fichier Dockerfile peut produire une couche, selon la nature de l’instruction et la manière dont la construction est exécutée. Ces couches sont ensuite réutilisées lorsque leur contenu reste identique.
Ce mécanisme accélère les constructions et limite les transferts lorsque le cache est exploitable. Il crée aussi une illusion fréquente: une image finale peut sembler raisonnable lorsqu’on regarde sa taille locale, alors que le registre conserve des dizaines de versions et plusieurs couches partagées entre les balises.
Les sources de volume les plus courantes sont prévisibles:
- utiliser une image de base complète alors que l’application n’a besoin que de quelques bibliothèques d’exécution;
- installer les outils de compilation dans l’image finale;
- copier tout le dépôt avec
COPY.., notamment les dépendances locales, les fichiers de test et les répertoires générés; - produire une nouvelle balise pour chaque construction sans supprimer les anciennes;
- conserver les images de validation, de branche et de démonstration sans durée de vie définie;
- télécharger des dépendances dans plusieurs couches au lieu de les regrouper avec le nettoyage correspondant;
- publier des images pour plusieurs architectures sans mesurer la consommation totale du manifeste et des couches;
- stocker dans le registre des images qui ne sont plus référencées par aucun environnement.
Une image Docker trop lourde dégrade aussi le cycle de livraison. Elle demande davantage de temps pour être construite, poussée et tirée. Le coût n’apparaît donc pas seulement sur la ligne stockage. Il se manifeste dans la durée des tâches d’intégration continue, la consommation de bande passante et le temps nécessaire au démarrage d’une nouvelle instance.
Une image volumineuse n’est pas seulement un problème de stockage. C’est une dette répétée à chaque compilation, chaque déploiement et chaque téléchargement.
Distinguer la taille locale de la consommation du registre
La commande d’analyse utilisée par une équipe de développement ne donne pas toujours la vision financière correcte. Une image locale peut partager une grande partie de ses couches avec d’autres images. À l’inverse, des fichiers apparemment supprimés peuvent rester présents dans une couche historique déjà envoyée au registre.
Il faut examiner au minimum:
- la taille compressée réellement transférée vers le registre;
- le nombre de couches par image;
- la fréquence de publication;
- le nombre de balises actives;
- les images sans déploiement associé;
- les téléchargements par région et par environnement;
- les couches communes entre les variantes d’une même application.
Le stockage facturé dépend de ce que le registre conserve. La durée d’utilisation d’une balise ne suffit pas à déterminer la valeur d’une image. Une balise peut être supprimée alors que les couches restent référencées par une autre image. À l’inverse, une image complète peut disparaître sans libérer immédiatement chaque octet si d’autres manifestes utilisent encore les mêmes couches.
Cette distinction est essentielle pour éviter les faux diagnostics. Supprimer des balises réduit la visibilité opérationnelle. Supprimer des manifestes et des couches devenus orphelins réduit réellement le stockage, à condition que le registre applique correctement son mécanisme de nettoyage.
L’illusion du nettoyage: comprendre la persistance des couches Docker
Le piège le plus répandu se trouve dans le fichier Dockerfile. Une équipe installe des paquets, crée des fichiers temporaires, puis les supprime dans une instruction ultérieure. Le fichier final semble propre. La couche précédente, elle, conserve les données qui y ont été écrites.
Docker ne réécrit pas l’historique de l’image à chaque instruction. Une suppression effectuée dans une couche ultérieure masque le fichier dans le système de fichiers résultant, mais ne retire pas automatiquement les données de la couche précédente. Le registre conserve donc le contenu historique tant que cette couche reste référencée.
Un schéma classique produit ce résultat:
- installer les dépendances dans une première instruction;
- télécharger ou extraire des archives;
- supprimer les archives dans une seconde instruction;
- conserver malgré tout leur contenu dans la couche précédente.
La correction consiste à regrouper l’installation et le nettoyage dans la même instruction lorsque ces opérations concernent des fichiers temporaires. Le nettoyage ne doit pas être une action cosmétique placée à la fin du fichier. Il doit intervenir avant la création de la couche qui sera envoyée au registre.
Ce que l’analyse du fichier Dockerfile doit révéler
Un examen technique efficace ne commence pas par la suppression arbitraire de lignes. Il cherche les frontières qui créent du volume durable.
Les points à isoler sont les suivants:
- les instructions qui installent un gestionnaire de paquets;
- les compilateurs, en-têtes et bibliothèques uniquement nécessaires à la construction;
- les archives téléchargées pendant l’installation;
- les répertoires de cache des gestionnaires de dépendances;
- les fichiers de documentation et de tests copiés dans l’image d’exécution;
- les changements fréquents placés avant les dépendances stables;
- les secrets ou fichiers de configuration qui ne doivent jamais être intégrés à une couche;
- les répertoires ignorés par le fichier
.dockerignore.
Le fichier .dockerignore a un effet direct sur le contexte envoyé au moteur de construction. Sans lui, un dépôt peut transmettre le répertoire .git, les dépendances locales, les journaux, les artefacts de compilation ou les fichiers d’environnement. Même si ces fichiers ne finissent pas dans l’image finale, ils augmentent le contexte, ralentissent la construction et peuvent invalider le cache.
Il faut exclure au minimum ce qui n’a aucune fonction dans la construction:
.gitet les métadonnées du dépôt;- les répertoires de dépendances générés localement;
- les sorties de compilation;
- les journaux et fichiers temporaires;
- les environnements locaux;
- les fichiers contenant des secrets;
- les ressources de test lorsqu’elles ne sont pas nécessaires à la construction.
Cette étape ne remplace pas l’optimisation des couches. Elle réduit le volume d’entrée et évite des invalidations de cache inutiles.
Stratégies de construction: réduire la taille des images de 60 % à 90 %
Les constructions multi-étapes constituent le levier le plus structurant. Elles séparent l’environnement de compilation de l’environnement d’exécution. Le premier peut contenir un compilateur, les outils du gestionnaire de paquets, les dépendances de développement et les utilitaires nécessaires à la génération de l’application. Le second ne reçoit que le résultat utile.
Le principe est simple: compiler dans une étape, copier l’artefact produit dans une image finale minimale. Il ne s’agit pas de nettoyer une image complète après coup. Il s’agit d’éviter que les outils de construction y soient présents.
Cette méthode réduit généralement la taille finale de 60 % à 90 %, selon la pile logicielle et la proportion d’outils retirés. La réduction exacte dépend du langage, des dépendances natives, du serveur utilisé et du contenu réellement nécessaire au fonctionnement.
Image complète contre image d’exécution minimale
| Paramètre | Image complète | Image d’exécution minimale |
|---|---|---|
| Contenu | Outils de compilation, gestionnaires de paquets, bibliothèques de développement | Binaire ou fichiers générés et dépendances d’exécution |
| Taille finale | Élevée, car l’environnement de construction est conservé | Réduite, si l’application supporte l’environnement choisi |
| Surface d’attaque | Plus large, avec davantage d’outils disponibles | Plus limitée, avec moins de composants exploitables |
| Diagnostic | Plus simple au premier abord, car les utilitaires sont présents | Plus exigeant; les outils doivent rester à l’extérieur de l’image |
| Temps de transfert | Plus long à chaque téléchargement | Plus court, notamment lors d’un déploiement à froid |
| Risque principal | Accumulation de composants inutiles | Incompatibilité avec une bibliothèque ou un mécanisme d’exécution attendu |
Les images Alpine et Distroless peuvent réduire fortement le poids de l’image finale. Elles ne sont pas interchangeables.
Alpine repose sur une distribution minimale. Elle conserve un environnement d’exécution plus proche d’un système Linux classique, mais certaines bibliothèques et certains comportements diffèrent d’une distribution fondée sur la bibliothèque GNU standard. Une application compilée ou dépendante de bibliothèques natives peut nécessiter des adaptations.
Distroless va plus loin dans la réduction de l’environnement. L’image contient généralement les éléments nécessaires à l’exécution, sans fournir un ensemble complet d’outils d’administration. Le bénéfice est clair en matière de surface et de volume. Le diagnostic dans un conteneur en panne devient en revanche plus contraint. Il faut concevoir l’observabilité avant le déploiement, et non compter sur un interpréteur de commandes présent par défaut.
Le choix d’une image minimale doit donc répondre à une contrainte d’exécution. Réduire la taille à tout prix peut déplacer la dette vers le dépannage, la compatibilité ou la gestion des certificats.
Organiser les instructions pour conserver le cache utile
L’ordre des instructions influence directement le temps de construction. Une modification fréquente placée avant l’installation des dépendances invalide les couches suivantes. Le moteur doit alors recommencer une partie importante du travail.
Une organisation robuste suit généralement cette logique:
1. sélectionner une image de base adaptée à la phase concernée;
2. copier les fichiers de définition des dépendances;
3. installer les dépendances reproductibles;
4. copier le code source;
5. compiler ou générer l’artefact;
6. transférer uniquement l’artefact vers l’image finale;
7. définir un utilisateur non privilégié et la commande de démarrage.
Le principe n’est pas de minimiser le nombre d’instructions de manière dogmatique. Les couches ont une fonction de cache. Une fusion excessive peut rendre chaque modification plus coûteuse. À l’inverse, des instructions dispersées créent des couches inutiles et rendent le comportement du cache difficile à prévoir.
Le bon compromis consiste à séparer les étapes selon leur fréquence de modification et leur rôle. Les dépendances stables doivent être isolées du code applicatif qui change souvent. Les fichiers temporaires doivent être créés et supprimés dans la même couche. Les artefacts de construction doivent traverser la frontière entre les étapes, pas l’ensemble de l’environnement.
Ne pas copier ce que l’application n’utilise pas
COPY.. est pratique. Il est aussi responsable d’une part importante des images surdimensionnées. Cette instruction transfère tout ce que le contexte de construction n’exclut pas.
Le dépôt doit être traité comme une source, pas comme un contenu à embarquer par défaut. Pour chaque répertoire copié, il faut répondre à une question concrète: l’application en a-t-elle besoin pendant l’exécution, ou seulement pendant la construction et les tests?
Les tests, les exemples, les fichiers de documentation et les configurations locales ne doivent pas se retrouver dans l’image finale sans justification. La présence de ces fichiers augmente le volume et peut exposer des informations internes. Dans un contexte DevSecOps, cette erreur relève autant de l’hygiène de sécurité que de l’optimisation financière.
Automatisation du cycle de vie: piloter la rétention pour diviser la facture
Alléger une image ne suffit pas si le registre en conserve des centaines. Les balises de développement sont souvent le point de fuite principal. Chaque branche peut publier plusieurs variantes, chaque exécution d’intégration continue peut produire une nouvelle image et chaque correction peut laisser une balise supplémentaire.
Sans politique de rétention, le registre devient un historique permanent de la chaîne de construction. Cet historique n’a pas toujours de valeur opérationnelle. Une image qui ne peut plus être redéployée, qui correspond à un commit abandonné ou qui n’est référencée par aucun environnement ne devrait pas rester indéfiniment dans le stockage principal.
Les règles de cycle de vie d’Amazon ECR permettent de supprimer automatiquement les anciennes balises selon des critères définis. Correctement paramétrées, elles peuvent réduire les coûts de stockage de 50 % à 80 %. Cette fourchette ne constitue pas une économie garantie. Elle dépend du volume initial, de la cadence de publication, de la durée de conservation et de la quantité d’images réellement inutiles.
Concevoir une politique de rétention par fonction
Une politique unique pour toutes les images est rarement adaptée. Les versions de production et les images de test n’ont pas la même valeur.
Une segmentation opérationnelle peut distinguer:
- les versions de production, conservées selon la capacité de retour arrière et les exigences d’audit;
- les versions candidates, conservées pendant la période de validation;
- les images de branche, conservées pendant la durée utile du développement;
- les images de construction intermédiaires, supprimées rapidement lorsqu’elles ne servent plus au cache;
- les images de diagnostic, conservées uniquement si un incident ou une analyse les justifie.
Le nombre de balises ne doit pas être le seul critère. Une règle fondée uniquement sur l’ancienneté peut supprimer une version importante peu téléchargée. Une règle fondée uniquement sur le nombre peut conserver des images anciennes dans un dépôt peu actif pendant une longue période.
Il faut définir la rétention autour d’un usage mesurable:
- pouvoir restaurer la dernière version stable;
- conserver les versions nécessaires à une procédure de retour arrière;
- préserver les images liées à un audit ou à un incident;
- éliminer les balises temporaires après la fin du pipeline;
- empêcher les balises de production d’être traitées comme des artefacts de test.
Le marquage immuable des versions de production complète cette stratégie. Une balise qui peut être réécrite rend le diagnostic plus difficile et affaiblit la traçabilité. Les balises temporaires peuvent suivre une convention différente, mais leur durée de vie doit rester explicite.
Le registre public ne rend pas la gouvernance facultative
Certains registres publics proposent un quota de stockage gratuit, notamment jusqu’à 50 Go par mois pour les registres publics AWS ECR selon les conditions applicables. Cela ne signifie pas que la conservation est sans limite ni que les téléchargements sont sans coût.
Les coûts peuvent réapparaître par:
- le stockage au-delà du quota;
- les téléchargements fréquents;
- les transferts entre régions;
- les appels exécutés depuis des environnements extérieurs au réseau attendu;
- la duplication de la même image dans plusieurs registres.
La logique de rétention reste donc nécessaire, même lorsque le prix direct du stockage paraît faible. Une architecture qui dépend d’un quota gratuit sans surveiller la croissance du volume ne dispose pas d’un modèle de coût. Elle reporte simplement la découverte du problème.
Au-delà du stockage: l’impact invisible de la bande passante multi-régions
Le stockage attire l’attention parce qu’il apparaît comme une ligne récurrente. Le transfert de données peut devenir plus difficile à attribuer. Une image tirée depuis une autre région génère des frais supplémentaires de bande passante à chaque téléchargement, selon le chemin réseau et les services impliqués.
Le phénomène est amplifié par les déploiements à froid, les mises à l’échelle automatiques et les nœuds qui ne disposent pas encore des couches nécessaires. Une image de plusieurs centaines de mégaoctets n’est pas seulement conservée dans le registre. Elle peut être téléchargée plusieurs fois par plusieurs instances, parfois depuis plusieurs zones géographiques.
La réduction de taille agit alors sur trois variables:
- le temps nécessaire au téléchargement;
- la consommation de bande passante;
- la latence avant disponibilité du conteneur.
Une stratégie de réplication régionale peut réduire la distance réseau, mais elle augmente le nombre de copies conservées. Il faut donc arbitrer entre le coût de stockage supplémentaire et le coût des transferts répétés.
Comparer les leviers d’optimisation
| Levier | Bénéfice principal | Coût ou risque | Priorité |
|---|---|---|---|
| Politique de rétention | Réduire le volume historique du registre | Supprimer une image encore utile si les règles sont trop agressives | Élevée |
| Construction multi-étapes | Exclure les outils de compilation de l’image finale | Complexité accrue du fichier de construction | Élevée |
| Image de base minimale | Réduire le poids et la surface d’attaque | Incompatibilités possibles avec certaines bibliothèques | Élevée |
Fichier .dockerignore | Réduire le contexte et les invalidations de cache | Oublier un fichier requis par la construction | Élevée |
| Réplication régionale | Réduire certains transferts longue distance | Dupliquer le stockage et la gouvernance | Selon l’architecture |
| Nettoyage manuel | Corriger rapidement une accumulation ponctuelle | Processus non fiable et non reproductible | Temporaire |
| Compression ou réduction d’artefacts | Diminuer le volume transféré | Gain variable selon le format et l’application | Selon le cas |
Le coût stockage registre Docker optimisation ne se traite donc pas avec une commande isolée. Il faut relier le fichier de construction, le registre, le pipeline d’intégration continue et la topologie de déploiement.
Mettre en place une démarche de réduction sans casser la livraison
La première erreur serait de supprimer immédiatement les images anciennes. Avant toute purge, il faut établir la relation entre les images et les environnements qui les utilisent.
Une démarche maîtrisée suit plusieurs étapes:
1. Mesurer le stock existant.
Relever la taille des dépôts, le nombre de balises, la croissance mensuelle et les images les plus volumineuses. Séparer les versions de production des artefacts temporaires.
2. Identifier les références actives.
Vérifier les définitions de déploiement, les tâches planifiées, les environnements de préproduction et les procédures de retour arrière. Une balise visible n’est pas nécessairement la seule référence utile.
3. Classer les images par durée de vie.
Une version stable, une image de branche et une image intermédiaire ne doivent pas suivre la même règle. Formaliser cette différence dans les conventions de marquage.
4. Corriger les fichiers de construction.
Introduire les constructions multi-étapes, réduire le contexte, déplacer les outils de compilation hors de l’image finale et regrouper les opérations temporaires avec leur nettoyage.
5. Comparer avant et après.
Mesurer la taille compressée, le temps de construction, le temps de téléchargement et la fréquence d’utilisation du cache. Une réduction de taille qui détruit le cache ou augmente fortement le temps de compilation n’est pas nécessairement un bon compromis.
6. Déployer la rétention progressivement.
Commencer par les balises clairement temporaires. Observer les effets. Étendre ensuite la règle aux versions plus anciennes en conservant une capacité de retour arrière cohérente.
7. Surveiller la dérive.
Ajouter des seuils sur la taille des images, le volume du registre et la croissance des balises. Une optimisation ponctuelle ne résiste pas longtemps à un pipeline qui republie sans contrôle.
Intégrer le coût dans le pipeline
Le pipeline d’intégration continue est le point de contrôle le plus efficace. Il connaît la branche, le commit, le type d’environnement et la durée de vie attendue de l’image. Il peut donc appliquer des conventions distinctes selon le contexte.
Une politique cohérente peut imposer:
- une balise immuable pour les versions livrées;
- une balise temporaire pour les validations;
- une suppression automatique des images liées aux branches supprimées;
- un seuil de taille déclenchant une analyse avant publication;
- une génération de rapport sur les couches les plus volumineuses;
- l’interdiction d’intégrer des secrets dans le contexte ou les couches;
- une séparation stricte entre image de construction et image d’exécution.
Le contrôle de taille ne doit pas être interprété mécaniquement. Une image plus importante peut être justifiée par une bibliothèque native ou une exigence de compatibilité. Elle doit toutefois être documentée. Sans justification, toute augmentation devient une régression silencieuse.
L’analyse doit également porter sur les dépendances. Une mise à jour peut ajouter une bibliothèque lourde, modifier le format d’un artefact ou désactiver la réutilisation d’une couche. Le problème n’est pas toujours dans le Dockerfile. Il peut se trouver dans le graphe de dépendances produit par l’application.
Ce que la réduction d’image ne doit pas sacrifier
Réduire la facture ne consiste pas à supprimer tout ce qui n’est pas strictement nécessaire au démarrage. Certains éléments ont une fonction de sécurité ou d’exploitation.
Les certificats racine, les fichiers de fuseau horaire, les bibliothèques de résolution de noms et les composants nécessaires à la validation TLS doivent être traités comme des dépendances d’exécution. Leur absence peut produire une image légère mais inutilisable dans un réseau réel.
La journalisation et l’observabilité doivent également rester disponibles. Une image Distroless peut être pertinente si l’application expose des métriques, produit des journaux exploitables et dispose d’une procédure de diagnostic externe. Elle est moins adaptée si l’équipe dépend d’une intervention interactive dans le conteneur pour comprendre chaque incident.
Il faut enfin vérifier le comportement du processus principal:
- exécuter l’application avec un utilisateur non privilégié lorsque cela est compatible;
- déclarer clairement le point d’entrée;
- éviter les scripts auxiliaires inutiles;
- tester les signaux d’arrêt et le comportement de redémarrage;
- vérifier la résolution des certificats et des noms;
- reproduire le lancement dans un environnement aussi proche que possible de la production.
Une image plus petite mais impossible à diagnostiquer transfère le coût vers l’exploitation. Le registre devient moins cher. L’incident devient plus long.
La position à tenir sur le coût du registre Docker
Le stockage d’images Docker est un coût d’architecture, pas une simple dépense de plateforme. Les couches sont produites par le fichier de construction, conservées par la politique de cycle de vie et transférées selon la topologie réseau. Ces trois mécanismes doivent être traités ensemble.
Les gains les plus immédiats viennent généralement de deux actions: supprimer les artefacts sans durée de vie et séparer l’environnement de construction de l’image finale. Les règles de rétention peuvent réduire les coûts du registre de 50 % à 80 %. Les constructions multi-étapes et les images minimales peuvent réduire la taille finale de 60 % à 90 %. Ces chiffres donnent un ordre de grandeur, pas une promesse universelle.
La mesure doit rester le juge. Comparer le volume avant et après, vérifier les temps de pipeline, observer les transferts régionaux et tester les procédures de retour arrière. Une optimisation qui n’est pas mesurée finit par devenir une nouvelle convention opaque.
Vérifications finales
- Mesurer la taille compressée des images, pas uniquement leur taille locale.
- Identifier les balises temporaires et leur durée de vie réelle.
- Configurer une politique de rétention adaptée aux environnements.
- Utiliser une construction multi-étapes pour exclure les outils inutiles.
- Choisir une image de base minimale après validation de compatibilité.
- Regrouper l’installation et le nettoyage des fichiers temporaires dans la même couche.
- Maintenir un fichier
.dockerignorestrict. - Éviter de copier le dépôt complet dans l’image d’exécution.
- Contrôler les transferts entre régions et les téléchargements lors des montées en charge.
- Conserver les versions nécessaires au retour arrière et à l’audit.
- Ajouter un seuil de taille dans le pipeline.
- Recalculer régulièrement le coût du stockage, de la bande passante et du temps de construction.
Le registre ne devient pas coûteux parce que Docker serait inefficace. Il le devient lorsque la chaîne de livraison ne distingue plus une version exploitable d’un déchet historique. Réduire les couches inutiles, limiter leur durée de vie et rapprocher les images des environnements qui les consomment suffit souvent à reprendre le contrôle. La méthode est connue. L’écart se situe dans son automatisation.