1. Image hygiene : le sens
Quand on entend « hygiène de l’image », le cerveau d’un débutant imagine souvent une seule scène : on se met au régime, on jette tout ce qui se trouve dans l’image et on célèbre la perte de 200 mégaoctets. En réalité, image hygiene n’est pas du bodybuilding, mais plutôt une cuisine propre : pour pouvoir cuisiner rapidement, de manière reproductible et sans s’empoisonner avec un « assaisonnement » que quelqu’un a laissé sur la table il y a une semaine.
Le sens le plus important de l’hygiène dans notre cours est de rendre l’image prévisible. La prévisibilité ne commence pas par « le plus petit Linux », mais par la compréhension de l’origine des fichiers dans la construction, pourquoi l’image sur votre machine coïncide avec celle de votre collègue, et pourquoi la construction ne casse pas à cause d’un détail comme « quelqu’un a déposé un énorme CSV dans le dossier data/exports/ ».
Pour mieux garder le tableau en tête, il est utile de penser à l’hygiène comme à trois niveaux : le contexte de build propre (ce qui est envoyé à la construction), l’image d’exécution propre (ce qui reste dans la couche finale) et les références à la base propres (quelles images de base vous utilisez et à quel point elles dérivent dans le temps). Le tout est complété par un quatrième « accélérateur de bon sens » : des vérifications rapides pour attraper les erreurs plus tôt.
Un petit tableau pour se repérer :
| Couche d’hygiène | Question que nous nous posons | « Odeur » typique du problème |
|---|---|---|
| Contexte de build | « Que Docker voit-il exactement lors de docker build ? » | Contexte de 500 Mo, un fichier .env parti involontairement, la construction ralentit |
| Image d’exécution | « Qu’est-ce qui arrive réellement dans l’image finale ? » | Dans l’exécution se trouvent les sources/gradle/fichiers temporaires |
| Images de base | « Dans quelle mesure la base est-elle reproductible ? » | Aujourd’hui ça marche, dans une semaine ça casse soudainement |
| Vérifications rapides | « Peut-on détecter le problème sans une construction complète ? » | L’erreur n’est visible qu’après une longue construction |
2. Contexte de build : composition
Avec le contexte de build, l’important ici n’est plus la découverte, mais la révision. Dans le modèle final, ce sont souvent des éléments qui rendent l’image imprévisible qui partent dans la construction via le contexte. Docker prend tout le contexte en bloc, donc des dossiers build/, .gradle/, des fichiers IDE, des exports et des fichiers secrets locaux superflus nuisent à la fois à la vitesse et à la reproductibilité.
Pour un projet Java/Gradle, c’est particulièrement visible : le contexte gonfle rapidement, et un COPY . . involontaire emprisonne ensuite dans l’image ce qui n’aurait pas dû participer à la construction. C’est pourquoi on examine le contexte de build à ce stade non pas comme une théorie du début du cours, mais comme un audit rapide d’hygiène : ce que Docker voit réellement et ce que nous ne voulons absolument pas envoyer dans la construction.
Dans Container-Ready Catalog Service, le contexte reste la racine du dépôt, et cela suffit. Mais cela ne fonctionne qu’avec un .dockerignore strict.
3. .dockerignore : filtrage du contexte
Dans le modèle final, .dockerignore agit comme le dernier filtre avant la construction. Il n’a pas besoin d’être énorme, mais il doit couper les déchets de construction, les données d’exécution et les secrets locaux qui ne devraient même pas apparaître dans le contexte.
# Cache Gradle et artefacts de construction – on ne les emmène pas dans le contexte
.gradle/
build/
# Paramètres IDE – inutiles pour la construction
.idea/
*.iml
# Métadonnées Git et déchets du système d’exploitation
.git/
.DS_Store
# Secrets et configurations locales d’exécution
.env
config/application-secrets*.yml
secrets/
# Exports d’exécution
data/exports/*
!data/exports/.gitkeep
Remarquez que nous excluons les vrais fichiers secrets et les données d’exécution, pas leurs modèles. .env.example et application-secrets.yml.example peuvent rester dans le dépôt, tandis que .env, les fichiers application-secrets*.yml locaux et le répertoire secrets/ n’ont aucune raison d’être dans le contexte de build.
Et inversement : il ne faut pas toucher à gradlew, au dossier gradle/, aux scripts de construction ni aux sources. .dockerignore n’est utile que lorsqu’il coupe le superflu sans casser l’étape builder.
4. Image de base : tag vs digest
Quand on dit « image basée sur eclipse-temurin:25-jre-jammy », on pense à une chose concrète. En pratique, le tag (tag) est plutôt un pointeur qu’un objet en béton armé. Aujourd’hui, il peut pointer vers un ensemble de couches, et dans un mois vers un autre (par exemple après une mise à jour de sécurité ou une reconstruction de l’image de base). C’est normal dans l’industrie. Ce qui ne l’est pas, c’est de vouloir la reproductibilité tout en n’utilisant que des pointeurs flottants, puis de s’étonner que tout marchait jeudi et que « soudainement » ça ne marche plus lundi.
Le digest, c’est déjà une « adresse précise ». Il ressemble à @sha256:... et désigne un artefact immutable concret. Si vous figez la base via digest, vous obtenez une construction « comme sur des roulettes » : même entrée, même résultat. C’est très utile pour un modèle pédagogique et pour le développement en équipe, où « ça marche chez moi » ne doit pas dépendre du jour de la semaine.
Pour le contexte (sans entrer dans le suivi de la chaîne d’approvisionnement), on peut retenir une règle simple. Le tag est pratique quand on lit un Dockerfile des yeux. Le digest est utile quand on veut que le Dockerfile ne devienne pas une loterie. En équipe réelle, on fait souvent un compromis : on met en place une politique « on met à jour le digest consciemment » plutôt que « il dérive tout seul ».
Un mini‑exemple de fragment de Dockerfile où l’image de base de l’étape d’exécution est fixée par digest :
# On fige la base non seulement par tag, mais aussi par digest pour la reproductibilité
FROM eclipse-temurin:25-jre-jammy@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
# Répertoire de travail dans le conteneur
WORKDIR /app
# On copie uniquement l’artefact compilé de l’application
COPY application.jar application.jar
# On lance le service avec java -jar
ENTRYPOINT ["java", "-jar", "application.jar"]
Remarquez que ce n’est ni une « magie de sécurité » ni un « scan du monde pour les vulnérabilités ». C’est simplement une discipline : vous fixez ce que vous considérez comme base et vous ne le laissez pas changer en silence.
Le même principe s’applique à l’environnement Compose : PostgreSQL, Redis, RabbitMQ existent aussi sous forme d’images, et si vous laissez des versions trop flottantes, vous modifiez « par hasard » l’infrastructure sous vos pieds. Mais nous restons dans les limites du cours : il nous suffit de comprendre la logique tag vs digest, sans construire un système complet de chaîne d’approvisionnement.
Comment obtenir le digest
En général, la première question après avoir parlé du digest est très terre-à-terre : « OK, mais où le trouver ? Je ne vais pas inventer un sha256 à vue d’œil. » Non, et c’est bien : le digest se prend de Docker, pas de l’inspiration.
Le chemin le plus simple et accessible aux juniors : d’abord télécharger explicitement l’image par tag, puis regarder quel digest elle a dans le cache local. Cela ressemble à ceci :
# On télécharge l’image par tag (pour qu’elle apparaisse dans le cache local)
docker pull eclipse-temurin:25-jre-jammy
# On regarde quel digest immutable se trouve dans l’image téléchargée
docker image inspect eclipse-temurin:25-jre-jammy \
--format='{{index .RepoDigests 0}}'
# Le résultat sera du genre : eclipse-temurin@sha256:7f3c... (exemple)
Ce n’est pas la seule méthode, mais elle est compréhensible : vous fixez d’abord ce que vous avez téléchargé, puis vous voyez l’identifiant immutable. Si RepoDigests affiche plusieurs valeurs, cela vient généralement du fait que l’image est disponible sous différents noms ou qu’elle a plusieurs références. Dans un projet pédagogique, il suffit de choisir la première, et en équipe, on se met d’accord sur les règles de sélection et de mise à jour.
Un autre point pratique qui refait surface chez les étudiants avec des plateformes mixtes (par exemple certains sur amd64, d’autres sur arm64). Si vous avez fixé un digest qui concerne une architecture spécifique, sur une autre architecture l’image pourrait ne pas se charger comme prévu. Le digest est donc un outil de discipline, mais il faut l’utiliser en connaissance de cause : si votre équipe est réellement multi-plateformes, vérifiez que la fixation choisie fonctionne dans les deux mondes. Nous n’entrons pas dans un pipeline multi-plateforme, mais « vérifier qu’on ne se tire pas une balle dans le pied » fait déjà partie de l’hygiène.
5. Vérification du Dockerfile : buildx --check
À un moment donné du développement professionnel, on acquiert la sagesse : mieux vaut un petit drapeau rouge en 10 secondes qu’un grand incendie rouge en 10 minutes. docker buildx build --check est exactement cette prévention. Il ne remplace pas une vraie construction et un lancement de conteneur, mais il aide à comprendre rapidement que le Dockerfile est au moins valide et ne contient pas de problèmes évidents.
Le plus simple est de le voir comme un linter/validateur rapide de Dockerfile. Il vérifie la syntaxe, la structure, la validité de certaines instructions et peut émettre des avertissements sur des anti-patrons courants. L’ensemble exact des vérifications peut varier selon les versions de Docker/buildx (les outils évoluent), donc il faut le considérer comme un « signal rapide » et non comme une expertise judiciaire.
Les commandes sont on ne peut plus simples, et c’est bien :
# Vérifier rapidement la validité du Dockerfile (sans construction réelle ni exécution)
docker buildx build --check .
# Pareil, mais pour une étape spécifique (par exemple runtime)
docker buildx build --check --target runtime .
La deuxième commande est particulièrement utile si votre Dockerfile comporte plusieurs étapes (builder, development, runtime, layered, etc.) et que vous souhaitez vérifier un chemin particulier. Cela s’intègre bien dans le style général de notre cours : un seul Dockerfile, plusieurs étapes, et vous les gérez explicitement.
Il est très important de ne pas confondre le sens. --check ne prouve pas que l’application démarre, se connecte à la base de données, écrit dans le répertoire d’exportation et répond aux requêtes HTTP. Tout cela se vérifie en lançant le conteneur et avec nos scénarios de smoke-test. Mais --check attrape très bien les erreurs « stupides » plus tôt : fautes de frappe, références incorrectes à une étape, endroits étranges où le Dockerfile ne s’analyse pas ou ne se construit pas logiquement. Et précisément le dernier jour du cours, une telle « inspection sanitaire » rapide fait gagner beaucoup de temps.
Un petit schéma de la façon dont cette vérification s’inscrit dans le pipeline général :
flowchart TD
%% Vérification rapide de santé du Dockerfile avant une construction lourde et un lancement
A[Corriger Dockerfile / .dockerignore] --> B[docker buildx build --check]
B -->|OK| C[docker build ...]
C --> D[docker run / docker compose up]
D --> E[smoke-check : logs + health + API]
B -->|erreur| F[corriger avant de commencer une longue construction]
La logique ici est simple : d’abord on vérifie rapidement que « le document n’est pas cassé », puis on fait le travail lourd, ensuite on lance et on vérifie le comportement.
6. Mini-hygiène de l’image finale
Sur la dernière ligne droite, on a très envie de dire : « Bon, ça marche comme ça, pourquoi chipoter ? » C’est un désir humain normal, surtout après vingt‑quatre jours de cours. Mais un modèle réutilisable se distingue de « ça a marché une fois » précisément par sa capacité à survivre après que vous ayez oublié pourquoi telle ou telle chose a été écrite.
Dans la pratique, la mini-hygiène de l’image finale consiste à retirer tout ce qui rend la construction aléatoire : les déchets superflus dans le contexte, les bases flottantes, les configurations de développement dans le chemin d’exécution et les variantes infinies de Dockerfile. Au lieu de cela, vous créez un chemin clair et lisible : voici le contexte, voici la construction, voici la base, voici la vérification.
Il est très bon que nous ayons déjà adopté le multi-stage comme canon : l’étape builder peut être même « sale » (Gradle, sources, cache), tandis que l’étape d’exécution reste propre. Cela en soi est hygiénique. Mais c’est aujourd’hui précisément que nous portons la discipline à un niveau où « le modèle peut être copié dans un nouveau projet sans honte ».
Et un autre point souvent sous-estimé : l’hygiène ne concerne pas seulement le Dockerfile, mais aussi « l’environnement autour ». Si vous conservez .env.example dans le dépôt, et que vous ne committez pas .env et ne l’envoyez pas dans le contexte de build, vous ne faites pas que « respecter les règles », vous rendez le projet pratique pour l’équipe. Si vous figez les images de base en connaissance de cause, vous réduisez la probabilité qu’un jour le comportement de la JVM dans le conteneur change « soudainement » à cause d’une mise à jour de la base. Ce n’est pas de la théorie : ce genre de choses arrive régulièrement dans des projets réels, mais la cause n’est pas toujours évidente — parfois c’est la base, pas le code.
7. Erreurs typiques lors de la mise en place de l’hygiène de l’image
Quand on amène l’image à un état soigné, les problèmes ne ressemblent généralement pas à « la sécurité abstraite s’est cassée ». Ils sont très concrets : la construction est devenue lente, la construction est devenue non reproductible, la construction est devenue bizarre et personne ne comprend pourquoi. Voici un ensemble d’erreurs qui se rencontrent particulièrement souvent à ce stade et qu’il est important de reconnaître par leurs symptômes, et non par un sentiment héroïque du style « bon, je vais corriger au hasard ».
Erreur n°1 : on a accidentellement retiré du .dockerignore ce dont l’étape builder a besoin.
C’est un classique : quelqu’un voit le dossier gradle/ ou le fichier gradlew et pense « ce sont des déchets de construction, on enlève ». Ensuite l’étape builder ne peut pas exécuter ./gradlew bootJar. Le symptôme est généralement simple : la construction échoue dès la première étape avec une erreur “file not found” ou “permission denied”. On ne guérit pas par magie, mais par vérification : ce qui doit réellement être copié dans l’étape builder et ce que vous avez exclu du contexte.
Erreur n°2 : .env ou une configuration locale s’est retrouvée dans le contexte de build « comme ça, par hasard ».
Même si vous ne faites pas COPY . ., le simple fait qu’un fichier secret soit présent dans le contexte est une mauvaise habitude. Aujourd’hui vous ne le copiez pas, demain quelqu’un fera un refactoring et l’emportera accidentellement à l’intérieur de l’image. C’est un « accident lent » typique : il ne casse pas la construction immédiatement, mais crée un risque de secret cuit dans l’image, qu’il sera très difficile d’« extraire » ensuite de l’historique des images.
Erreur n°3 : l’image de base est définie par un tag flottant, mais vous attendez une reproductibilité en béton.
Si vous utilisez un hypothétique :latest ou un tag trop général et que vous voulez que « ça se construise toujours à l’identique », vous jouez à la loterie. Parfois cela se manifeste par « sur mon portable ça marche, sur le CI c’est cassé », parfois par « lundi les tests ont commencé à échouer alors que le code n’a pas été touché ». La cause peut être une mise à jour silencieuse de la base.
Erreur n°4 : on a fixé le digest sans penser aux plateformes mixtes.
Sur une machine tout va bien, sur une autre l’image ne se charge pas ou se charge différemment de ce qui était attendu. Cela arrive souvent avec les étudiants qui ont un Mac sur arm64 alors que le modèle a été testé sur amd64. Nous ne faisons pas du cours un track multi-plateforme, mais la règle est simple : si votre équipe est réellement multi-plateformes, vérifiez que la fixation de la base n’est pas devenue « pour une seule architecture ».
Erreur n°5 : docker buildx build --check est perçu comme un remplacement du lancement réel.
Parfois après un --check réussi, on veut cocher « fait » et passer à autre chose. Mais le check ne vérifie pas que l’application démarre, que les profils sont lus, que le port est redirigé, que l’endpoint health est vivant, que le répertoire d’exportation est accessible en écriture. Il vérifie le Dockerfile en tant que document, pas le service en tant que système. Donc --check est une prévention, pas une visite médicale avant les Jeux Olympiques.
GO TO FULL VERSION