Le tag latest est confortable.
On écrit un compose.yml, on lance docker compose up -d, et le service démarre. Pas besoin de choisir une version de PostgreSQL, Redis, Traefik, Gitea, Vaultwarden ou n’importe quelle application auto-hébergée.
Le problème, c’est que latest ne veut pas dire “dernière version stable adaptée à mon environnement”. Ça veut seulement dire : “ce tag pointe vers quelque chose dans le registre au moment où Docker le résout”.
Ce quelque chose peut changer sans que votre fichier Compose change.
Pour une stack de test, ce n’est pas très grave. Pour une base de données, un reverse proxy exposé, un service d’authentification ou une application avec des volumes persistants, c’est une mauvaise convention d’exploitation.
Le vrai sujet n’est pas Docker. C’est la reproductibilité.
En bref#
latestest un tag mutable. Le mainteneur de l’image peut le faire pointer vers une autre image.- Une image sans tag explicite, comme
postgres, revient en pratique à tirerpostgres:latest. - Docker Compose accepte les tags (
postgres:16) et les digests (postgres:16@sha256:...). - Le risque principal n’est pas uniquement la sécurité. C’est surtout l’imprévisibilité des redéploiements.
- Une stack Compose exploitable doit dire clairement quelle version elle attend.
- En homelab, un tag de version explicite suffit souvent. Pour les services critiques, un digest devient pertinent.
- Les mises à jour doivent être visibles dans Git, relues, testées et réversibles.
Ce que latest veut dire#
Dans Docker, un tag est un pointeur lisible vers une image.
Ce pointeur peut changer. Docker le documente explicitement : les tags d’image sont mutables. Un éditeur peut donc publier une nouvelle image derrière un tag déjà connu.
C’est pratique pour distribuer automatiquement les derniers patchs d’une série. C’est beaucoup moins sain quand ce comportement devient implicite dans votre infra.
Dans Compose, ces deux formes sont problématiques :
services:
db:
image: postgres:latestservices:
db:
image: postgresLa seconde est souvent oubliée. Si aucun tag n’est fourni, Docker utilise :latest par défaut.
La forme minimale correcte ressemble plutôt à ceci :
services:
db:
image: postgres:16Et la forme la plus reproductible garde un digest :
services:
db:
image: postgres:16@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefLe digest ci-dessus est un exemple. Dans une vraie stack, il faut utiliser le digest réel fourni par le registre.
Ce que latest casse vraiment#
latest ne casse pas toujours immédiatement. C’est justement pour ça que l’habitude reste.
Le problème apparaît au mauvais moment :
- après un redémarrage de serveur ;
- pendant une migration vers une nouvelle machine ;
- au moment de restaurer une sauvegarde ;
- après un
docker compose pulllancé machinalement ; - quand Watchtower ou un équivalent recrée un conteneur ;
- quand un collègue ou vous-même relancez la stack six mois plus tard.
Prenons un exemple classique :
services:
app:
image: gitea/gitea:latest
restart: unless-stopped
volumes:
- ./gitea:/data
db:
image: postgres:latest
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/dataCe fichier ne raconte pas l’état attendu de la stack. Il raconte une intention vague : “prendre ce qui est considéré latest au moment du pull”.
En exploitation, ça pose plusieurs problèmes concrets :
- la version exacte n’est pas visible dans le dépôt ;
- un rollback ne peut pas simplement consister à revenir au commit précédent ;
- une restauration peut démarrer avec une version différente de celle qui a produit les données ;
- une migration applicative peut se déclencher sans que le changement soit explicite ;
- une panne devient plus difficile à diagnostiquer, car le YAML n’a pas forcément bougé.
Ce n’est pas une question de purisme. C’est le genre de détail qui fait perdre du temps quand un service refuse de repartir après une maintenance.
latest et sécurité : le malentendu#
L’argument habituel pour garder latest est simple :
Je veux recevoir les correctifs de sécurité.
C’est compréhensible, mais incomplet.
latest ne garantit pas que vos conteneurs déjà lancés sont corrigés. Un tag dans un fichier YAML ne met rien à jour tout seul. Il faut au minimum tirer l’image, recréer le conteneur et vérifier le service.
latest ne garantit pas non plus que le changement est limité à un correctif de sécurité. Vous pouvez récupérer :
- un patch applicatif ;
- une nouvelle version mineure ;
- une version majeure, selon la politique du projet ;
- une image reconstruite avec une base différente ;
- une modification de comportement non liée à la CVE qui vous intéresse.
La bonne approche n’est donc pas “ne jamais mettre à jour”. Ce serait pire.
La bonne approche, c’est de rendre la mise à jour visible :
- identifier les images utilisées ;
- suivre les releases ou advisories des services importants ;
- modifier les tags ou digests dans Git ;
- lire les notes de version quand le service a de l’état ;
- sauvegarder avant les migrations risquées ;
- déployer ;
- vérifier les logs, l’état des conteneurs et les endpoints ;
- garder une version précédente connue.
latest saute surtout les étapes où quelqu’un regarde ce qui change.
Le comportement Compose à connaître#
Docker Compose propose pull_policy pour contrôler quand une image est tirée.
Par défaut, quand l’image n’est pas construite localement, Compose tire l’image si elle n’existe pas déjà. La documentation précise aussi un point important : le tag latest est toujours tiré avec la politique missing.
Cette configuration reste donc plus mobile qu’elle n’en a l’air :
services:
nginx:
image: nginx:latest
pull_policy: missingEn développement, ce comportement peut rendre service.
Sur une stack durable, il faut plutôt séparer deux intentions :
- démarrer l’état déjà validé ;
- mettre à jour volontairement.
Un déploiement explicite ressemble davantage à ceci :
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100Pour un service précis :
docker compose pull app
docker compose up -d --no-deps app
docker compose logs --tail=100 appCes commandes ne remplacent pas une vraie procédure de release. Mais elles ont une qualité importante : le changement est intentionnel.
Auditer une stack Docker Compose#
Avant de corriger, il faut voir ce qui tourne.
Pour repérer les :latest explicites dans un dépôt :
rg -n 'image:\s*.*:latest' .Si rg n’est pas disponible :
grep -RInE 'image:[[:space:]]*.*:latest' .Pour repérer les images sans tag, regardez la configuration Compose résolue :
docker compose configCherchez les lignes image: qui ressemblent à postgres, redis, nginx ou gitea/gitea, sans :<tag> ni @sha256:<digest>.
Pour lister uniquement les images déclarées par la stack courante :
docker compose config --imagesPour comparer avec les conteneurs réellement lancés :
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"Pour voir les digests connus localement :
docker images --digestsPour inspecter une image précise :
docker image inspect nginx:1.27Sur les versions récentes de Compose, vous pouvez aussi produire une configuration avec digests résolus :
docker compose config --resolve-image-digestsCette commande ne remplace pas une politique de mise à jour, mais elle aide à voir vers quel contenu les tags résolvent au moment de l’audit.
Choisir le bon niveau de pinning#
Il n’y a pas une règle unique. Le niveau de précision dépend du risque opérationnel.
| Type de service | Exemple raisonnable | Commentaire |
|---|---|---|
| Test jetable | nginx:latest | acceptable si rien n’est persistant |
| Service interne peu critique | redis:7 | simple, lisible, suit une série majeure |
| Base de données | postgres:16 ou plus précis | jamais en latest pour une stack durable |
| Reverse proxy exposé | traefik:v3.2 | éviter les surprises de configuration |
| Application self-hosted | gitea/gitea:1.22.6 | version visible et rollback simple |
| Service critique | image:tag@sha256:... | reproductibilité forte, maintenance nécessaire |
Un tag de version majeure (postgres:16) est un compromis courant. Il permet de suivre les patchs d’une série sans passer silencieusement à PostgreSQL 17.
Un tag patch (gitea/gitea:1.22.6) donne un meilleur contrôle, mais demande de suivre plus activement les mises à jour.
Un digest est le plus strict. Docker permet de tirer une image par digest pour obtenir un contenu précis. En contrepartie, cette image ne recevra pas de mise à jour tant que le digest ne change pas.
Le digest est excellent pour la reproductibilité. Il est mauvais si vous l’utilisez comme excuse pour ne jamais patcher.
Exemple de correction#
Avant :
services:
app:
image: gitea/gitea:latest
restart: unless-stopped
volumes:
- ./gitea:/data
db:
image: postgres:latest
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/dataAprès :
services:
app:
image: gitea/gitea:1.22.6
restart: unless-stopped
volumes:
- ./gitea:/data
db:
image: postgres:16
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/dataCe changement ne rend pas la stack parfaite. Il rend le contrat plus clair.
Si une mise à jour casse l’application, Git permet de voir quelle version était utilisée avant. Si vous migrez vers une autre machine, vous ne dépendez plus du sens actuel de latest.
Bases de données : soyez plus strict#
Les bases de données méritent un traitement particulier.
Une image PostgreSQL, MariaDB, MySQL, MongoDB ou Redis n’est pas seulement un binaire. Elle manipule des données persistantes, parfois avec des formats, des extensions, des paramètres et des chemins de migration.
Avant une mise à jour de base de données :
- vérifiez la version actuelle ;
- lisez les notes de migration ;
- prenez une sauvegarde ;
- testez au moins une restauration sur un environnement séparé quand les données comptent vraiment ;
- évitez les sauts de version majeure non préparés.
Exemple de sauvegarde PostgreSQL générique :
docker compose exec db pg_dumpall -U postgres > backup-postgres.sqlAdaptez le nom du service, l’utilisateur et la méthode à votre stack. Et gardez en tête qu’une sauvegarde non testée reste une hypothèse.
Watchtower, auto-update et fausse tranquillité#
Les outils comme Watchtower peuvent être utiles. Ils deviennent dangereux quand ils masquent une absence de politique.
Mettre latest partout puis laisser un outil recréer automatiquement les conteneurs revient à accepter des changements non relus, non testés et parfois difficiles à corréler avec une panne.
Je l’éviterais pour :
- bases de données ;
- reverse proxies ;
- services d’authentification ;
- stockage ;
- applications exposées publiquement ;
- services avec migrations automatiques sensibles.
Un meilleur compromis consiste à automatiser les propositions de mise à jour, pas nécessairement leur application.
La nuance est importante : un outil qui informe ou ouvre une PR vous aide à garder le contrôle. Un outil qui redémarre tout seul peut transformer une mise à jour banale en panne difficile à dater.
Surveiller sans déployer automatiquement#
Remplacer latest par des versions explicites ne veut pas dire surveiller les releases à la main tous les soirs.
Il faut séparer trois actions :
- détecter qu’une nouvelle image existe ;
- évaluer si elle vous concerne ;
- déployer quand vous avez choisi de le faire.
latest mélange les trois. C’est précisément ce qu’on veut éviter.
Option 1 : Diun pour être notifié#
Diun fait une chose simple : surveiller des images de conteneurs et notifier quand un tag suivi change de digest.
C’est utile si vous voulez garder vos fichiers Compose simples, sans forcément mettre en place Renovate tout de suite.
Exemple minimal avec Docker :
services:
diun:
image: crazymax/diun:4
command: serve
restart: unless-stopped
volumes:
- ./diun:/data
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
- TZ=Europe/Paris
- DIUN_WATCH_WORKERS=10
- DIUN_WATCH_SCHEDULE=0 */6 * * *
- DIUN_PROVIDERS_DOCKER=true
- DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=trueCette configuration lit les conteneurs via le socket Docker et vérifie périodiquement les images. Sans DIUN_PROVIDERS_DOCKER_WATCHBYDEFAULT=true, Diun surveille seulement les conteneurs qui portent le label diun.enable=true.
Le point important : Diun ne redéploie pas. Il signale.
Ensuite, vous branchez une notification selon votre environnement : mail, Gotify, Matrix, Telegram, Slack, webhook ou autre. Le choix du canal importe moins que la discipline : une notification doit devenir une action traçable, pas un bruit de fond ignoré.
Il faut aussi comprendre sa limite : Diun est très pratique pour savoir qu’un tag que vous utilisez a changé. Pour proposer automatiquement de passer de gitea/gitea:1.22.6 à gitea/gitea:1.22.7, Renovate est plus adapté.
Option 2 : Renovate pour ouvrir des PR#
Renovate est plus adapté si votre compose.yml est dans Git.
Il sait extraire les images depuis les fichiers Docker Compose classiques (compose.yml, docker-compose.yml, variantes .yaml) et proposer les mises à jour sous forme de pull requests.
Renovate le fait déjà par défaut sur les fichiers qui correspondent à compose*.yml ou compose*.yaml. Un renovate.json minimal peut donc rester très sobre :
{
"extends": ["config:recommended"],
"packageRules": [
{
"matchDatasources": ["docker"],
"automerge": false
}
]
}L’intérêt n’est pas d’accepter toutes les PR automatiquement. L’intérêt est d’avoir :
- un diff lisible ;
- une version avant/après ;
- un historique Git ;
- un endroit naturel pour lire le changelog ;
- un rollback évident si le changement casse quelque chose.
Pour une infra personnelle, c’est souvent le meilleur compromis : automatiser la veille, garder l’humain sur le déploiement.
Scanner les images avant de patcher#
Les mises à jour d’images ne sont pas seulement une affaire de version applicative.
Une image contient aussi un système de base, des bibliothèques, parfois des binaires ajoutés par le mainteneur. Scanner l’image aide à repérer des vulnérabilités connues, même si ce n’est jamais une preuve que l’image est “sûre”.
Avec Trivy :
trivy image gitea/gitea:1.22.6Pour se concentrer sur les vulnérabilités importantes :
trivy image --severity HIGH,CRITICAL gitea/gitea:1.22.6Avec Docker Scout, si vous utilisez l’écosystème Docker correspondant :
docker scout cves gitea/gitea:1.22.6Ces scanners sont utiles, mais il faut garder du recul :
- ils dépendent de bases de vulnérabilités et de métadonnées paquet ;
- ils peuvent produire des faux positifs ;
- ils peuvent aussi manquer des problèmes ;
- ils ne remplacent pas les advisories du projet.
Le bon usage est pragmatique : scanner pour prioriser, pas pour déléguer toute la décision à un score CVSS.
Une routine de mise à jour propre#
Pour une stack Compose classique, une routine simple suffit souvent.
Commencez par regarder l’état actuel :
docker compose ps
docker compose config --images
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"Modifiez ensuite les tags dans compose.yml, puis déployez :
docker compose pull
docker compose up -d
docker compose psVérifiez les logs :
docker compose logs --tail=200Si un service expose HTTP :
curl -I https://exemple.examplePour un service critique, notez aussi la version avant et après quand l’image fournit une commande adaptée. Exemple avec Gitea :
docker compose exec app gitea --versionCette dernière commande dépend évidemment de l’image. Certaines applications exposent une commande version, d’autres seulement une page d’administration. Le principe reste le même : ne vous contentez pas d’un conteneur “Up”.
Un workflow réaliste ressemble à ça :
- Diun ou Renovate signale une nouvelle version.
- Vous regardez le changelog si le service est important.
- Vous scannez l’image cible si elle est exposée ou sensible.
- Vous sauvegardez les données si une migration est possible.
- Vous modifiez le tag dans Git.
- Vous lancez
docker compose pull. - Vous redémarrez le service.
- Vous vérifiez les logs, l’endpoint et la version.
- Vous gardez le rollback dans Git.
Ce n’est pas lourd. C’est juste explicite.
Checklist rapide#
Pour corriger une stack existante sans partir dans un chantier inutile :
- remplacez toutes les images sans tag ;
- remplacez tous les
:latestdurables ; - priorisez les bases de données et services exposés ;
- utilisez au minimum une version majeure explicite ;
- utilisez un tag patch ou un digest pour ce qui doit être strictement reproductible ;
- gardez le fichier Compose dans Git ;
- faites les mises à jour comme des changements visibles ;
- documentez le rollback quand le service est important ;
- automatisez les propositions, pas forcément les redémarrages.
La règle pratique tient en une phrase :
Si le service a des données ou reçoit du trafic réel,
latestn’a rien à faire dans son fichier Compose.
Impact réel#
Il faut rester nuancé.
latest dans un lab temporaire n’est pas un incident de sécurité. Pour tester une image pendant dix minutes, ce n’est pas le sujet.
Le risque augmente quand plusieurs conditions se cumulent :
- service exposé sur Internet ;
- données persistantes ;
- migrations automatiques ;
- sauvegardes non testées ;
- redéploiement automatisé ;
- absence d’historique Git ;
- plusieurs personnes capables de relancer la stack.
Dans ce contexte, latest transforme une opération banale en changement implicite.
Et les changements implicites sont mauvais pour l’exploitation. Ils rendent les pannes moins lisibles, les retours arrière moins évidents, et les audits moins fiables.
Conclusion#
latest est acceptable pour expérimenter. Il ne devrait pas être la convention par défaut d’une stack Docker Compose durable.
Utilisez des tags explicites. Pour les services sensibles, gardez un digest. Mettez à jour régulièrement, mais volontairement. Faites en sorte que le fichier Compose décrive l’état attendu, pas seulement une intention vague.
Ce n’est pas plus complexe. C’est juste plus honnête sur ce que vous exploitez réellement.
Sources#
- Docker Docs - Building best practices: Pin base image versions
- Docker Docs - Compose services: image
- Docker Docs - Compose services: pull_policy
- Docker Docs - docker image pull: Pull an image by digest
- Docker Docs - docker compose config
- Renovate Docs - Docker support
- Renovate Docs - Docker Compose manager
- Diun Docs - Docker provider
- Trivy Docs - Container image scanning
- Docker Docs - docker scout cves




