Faut-il utiliser les empreintes ou les tags pour les images Docker ?

J'ai entendu à plusieurs reprises qu'il était préférable d'utiliser l'empreinte des images. Il s'agirait là d'une bonne pratique avec laquelle je ne suis pas totalement en accord. Explorons les impacts.

Docker, conteneurs et images, c'est quoi déjà ?

Pour faire simple, Docker est un outil qui permet de gérer et d'exécuter des applications grâce à des conteneurs.

Les conteneurs, ce sont des boîtes virtuelles qui contiennent une application avec toutes ses dépendances, tout en l'isolant et en contrôlant ses interactions avec les autres conteneurs et le système d'exploitation (OS) de la machine hôte.

Enfin les images Docker contiennent les instructions et les fichiers qui permettent la création de conteneurs d'un type spécifique.

Et cette histoire d'empreinte et de tag ?

Lorsque l'on souhaite utiliser une image dans un projet, il existe plusieurs façons de la référencer, chacune avec ses avantages et ses inconvénients :

  1. Utiliser son empreinte (digest) :
    Par exemple : node@sha256:c7fd844945a76eeaa83cb372e4d289b4a30b478a1c80e16c685b62c54156285b
    L'empreinte, c'est en quelque sorte le numéro de série de l'image. Elle est unique et ne changera jamais pour cette version de l'image. Cela garantit de toujours utiliser la même image améliorant la stabilité ;

  2. Utiliser son petit nom :
    Par exemple node, nginx, debian...
    Cela permet d'utiliser la dernière version stable de l'image, mais peut amener des changements susceptibles de « casser » le fonctionnement du projet (breaking changes). En réalité, cela revient à utiliser le tag par défaut (latest), ce qui nous amène au prochain point ;

  3. Utiliser son nom + un tag (étiquette) :
    Par exemple : node:alpine, node:23-alpine, node:23.10-alpine, node:23 ou encore node:22
    Les tags, qui sont proposés par les équipes qui maintiennent l'image, servent à désigner une version (latest, dev, 22, 23, 23.10...) , une variante (debian, alpine, slim...) ou les deux (node:23-alpine).
    Mais un tag peut désigner des versions différentes de l'image dans le temps. Si aujourd'hui node:latest, node:23 et node:23.10 désignent la même version de l'image, ça ne sera pas forcément le cas demain. node:latest et node:23 pourraient désigner la version node:23:12 par exemple.
    De même, rien ne garantit que node:latest continuera d'être basée sur Debian dans un futur où elle perdrait en popularité.

Il faut garder tout cela en tête lorsqu’on veut choisir sa stratégie.

Pourquoi cette question ?

J'ai plusieurs fois entendu et lu (Exemple : Always Use Docker Image Digests par Craig Andrews) qu'il ne fallait utiliser que les empreintes (digest) pour les projets. Or, je ne suis pas d'accord avec cette pseudo bonne pratique qui amène un certain nombre de risques.

Empreintes versus Tags

Utilisation de l'empreinte :

  • ✅ Image de base immuable dans le temps ;
  • ✅ Idempotence des projets avec les mêmes paramètres ;
  • ✅ Amélioration de la stabilité des projets... tant que l'image existe dans le registre ;
  • ✅ Meilleures traçabilité des versions des dépendances lors de l'usage d'un gestionnaire de version ;
  • ⛔ Mises à jour « cassantes » plus complexes. Plus on attend, plus elles vont se cumuler et seront difficiles à corriger ;
  • ⛔ Risque accru de vulnérabilité ;
  • ⛔ Risque accru de voir l'image supprimée du registre ;
  • ⛔ Met l'accent sur le maintien à jour de l'empreinte, au détriment de la chaîne CI/CD (intégration et livraison continue).

Utilisation du tag :

  • ✅ Mises à jour (au moins de sécurité) à chaque reconstruction de l'image ;
  • ✅ Moins de risque de voir l'image supprimée du registre ;
  • ✅ Permet un équilibre entre stabilité et flexibilité en fonction du tag ;
  • ✅ Met l'accent sur la chaîne CI/CD, plutôt que sur le maintien à jour de l'empreinte ;
  • ✅ Possibilité d'être alerté lorsqu'un changement casse le projet via la chaîne CI/CD (intégration et livraison continue) ;
  • ✅ Mises à jour « cassantes » plus simples puisqu'elles peuvent être gérées petit à petit ;
  • ⛔ La construction d'une nouvelle version du projet peut ne plus fonctionner et nécessiter un correctif ;

Alors, qu'est-ce qu'on fait ?

Eh bien, à mon sens, ça dépend du contexte :

  • Environnement de développement :
    Si vous réalisez un test ou que vous êtes dans un environnement de développement, je pense qu'il faut utiliser les tags les plus ouverts possibles et régulièrement mettre à jour (docker pull) les images :

    • Cela permet aux développeurs de coder avec les bibliothèques les plus récentes ;
    • Les évolutions des dépendances sont intégrées au fur et à mesure ;
    • Les bugs liés à des mises à jour peuvent être détectés et corrigés plus rapidement ;
    • Des tests unitaires, automatisés lors d'un commit par exemple, peuvent compléter cette solution.
  • Création d'une image pour la stocker dans un registre
    Étant donné que vous construisez votre image pour la sauvegarder dans un registre, vous gardez un grand contrôle dessus. L'image ne changera pas tant que vous ne la reconstruisez pas.
    Dans ce cas, et à condition d'avoir une bonne chaîne CI/CD pour tester et ne mettre à jour l'image, il reste judicieux d'utiliser les tags les plus ouverts possibles pour les images de base.

  • Utilisation en production (ou recette) d'une image d'un répertoire :
    Dans ce cas, il est effectivement intéressant de pouvoir fixer la version utilisée. C'est encore plus vrai lorsque vous utilisez un orchestrateur de conteneurs (Docker Swarm/Kubernetes) qui peut reconstruire les conteneurs à tout moment. Mais cela peut être fait de plusieurs façons :

  • Un autre cas ?
    Dans ce cas, c'est à vous d’évaluer la meilleure approche. Gardez néanmoins en tête que la mise à jour des dépendances, si elle n'est pas automatisée dans une étape de la chaîne, nécessite la mise en place d'un processus. En l'absence, les mises à jour vont être oubliées et vous risquez de vous retrouver avec un projet contenant des failles mais également trop d'incompatibilités pour être mis à jour sereinement.

Pour aller plus loin ?

Ne pensez-vous pas qu'il aurait été intéressant de créer des Dockerfile lock ou des docker-compose lock (comme pour node) ? J'avais maladroitement plaidé en ce sens il y a quelques années.


Valentin LORTET

Cet article vous a plu ? N'hésitez pas à le partager :

Découvrir d'autres articles