Vous déployez. Ça fonctionnait hier. Aujourd’hui votre pipeline meurt avec manifest unknown, et le seul « changement »
est que quelqu’un a déplacé « latest » quelque part parce que « c’est bon, ce n’est qu’un tag ». Maintenant la production est bloquée en
ImagePullBackOff, votre canal d’incidents s’enflamme, et vous êtes sur le point d’apprendre la différence entre une
étiquette lisible par l’humain et un pointeur cryptographique.
La bonne nouvelle : manifest unknown n’est généralement pas mystérieux. C’est le registre qui dit « je ne trouve pas le
manifeste que vous avez demandé ». La mauvaise nouvelle : vous l’avez demandé d’une façon facile à rater. Rendons ça ennuyeux à nouveau.
Ce que signifie réellement « manifest unknown »
Dans l’univers Docker/OCI, une « image » n’est pas un unique blob. C’est un manifeste qui référence des layers et la config, stockés
dans un registre. Quand vous exécutez docker pull repo:tag, votre client demande au registre le manifeste
correspondant à cette référence.
« manifest unknown » est la façon dont le registre dit : « Je n’ai pas de manifeste pour ce nom+référence. »
Cette référence peut être :
- Un tag (
repo:1.2.3) - Un digest (
repo@sha256:…)
Si le registre ne peut pas résoudre le tag en un manifeste, ou ne trouve pas le digest, vous obtenez cette erreur. Ce n’est pas (généralement)
un timeout réseau. Ce n’est pas une erreur d’autorisation. C’est une absence de résolution.
Causes typiques en production :
- Ce tag n’a jamais existé dans ce registre (faute de frappe, mauvais projet, mauvaise région).
- Ce tag a existé, mais a été supprimé ou déplacé (politique de rétention, garbage collection, nettoyage humain).
- Vous avez poussé une image multi-arch incorrectement, donc le tag pointe vers quelque chose de différent de ce que le client attend.
- Vous tirez depuis le mauvais endpoint de registre (proxy/cache/miroir non aligné).
- L’autorisation est étrange et le registre renvoie volontairement « unknown » pour ne pas divulguer ce qui existe.
Un principe opérationnel à se tatouer sur l’intérieur des paupières :
les tags sont des pointeurs, pas des identités. Si vous les traitez comme des identités, votre pager vous traitera comme un loisir.
Tags vs digests : la vérité et les mensonges
Tags : conviviaux, mutables, faciles à casser
Un tag Docker est une étiquette chaîne associée à un manifeste dans un dépôt. Le registre stocke un mapping :
tag → manifest. C’est tout. Il n’y a aucune promesse que le mapping soit stable. Il n’y a aucune promesse que le
tag ne sera pas réécrit. Il n’y a aucune promesse que votre CI/CD ne fera pas une course avec un autre push.
Les tags sont parfaits pour les humains. Ils conviennent pour des workflows comme « promouvoir 1.2.3 en prod en le retaggant en prod. »
Ils sont terribles comme unique contrôle de la chaîne d’approvisionnement en production.
Digests : ennuyeux, immuables, la seule chose que vous pouvez prouver
Un digest (comme sha256:...) identifie un contenu précis. Quand vous pull par digest, vous demandez un
manifeste exact par son hash. Ce hash change si le contenu change. Donc le digest est effectivement immuable.
En termes pratiques :
- Pull par tag : « donnez-moi ce que le registre pense actuellement que
1.2.3est. » - Pull par digest : « donnez-moi ce manifeste exact, sans substitution. »
Quand votre incident est « ça marchait en staging mais pas en prod », la première chose que je cherche est la dérive de tag.
La deuxième est la dérive multi-arch. La troisième est de savoir si quelqu’un a « optimisé » le job de nettoyage du registre.
Blague n°1 : Les tags sont comme les étiquettes du frigo de bureau — utiles jusqu’à ce que quelqu’un décide « ça a l’air vieux » et jette votre déjeuner.
Que hache réellement le digest ?
Cela importe parce que les gens confondent « digest de layer » et « digest de manifeste » :
- Digest du manifeste : hash du document JSON du manifeste (qui référence layers/config). Puller par digest cible ceci.
- Digest des layers : hash de chaque blob de layer compressé. Ceux-ci se partagent entre images.
- Digest de la config : hash du JSON de configuration de l’image (env, entrypoint, historique, rootfs diff IDs, etc.).
« manifest unknown » concerne le manifeste (ou la manifest list) introuvable pour votre référence. Ce n’est pas une plainte sur un layer manquant — quand les layers manquent vous verrez d’autres erreurs (souvent 404 sur blob, ou échecs de téléchargement).
Manifestes, manifest lists, et pourquoi le multi-arch complique les choses
Les images mono-plateforme sont simples : un tag pointe vers un manifeste pour une plateforme (par exemple linux/amd64). Les images multi-arch
ajoutent un niveau : le tag pointe vers une manifest list (OCI index / Docker manifest list),
qui pointe ensuite vers des manifestes par plateforme.
Votre client fait de la négociation de contenu avec des en-têtes et choisit une entrée correspondant à sa plateforme (architecture, OS,
parfois variant). Si la manifest list n’inclut pas l’entrée pour la plateforme, vous pouvez voir des erreurs qui ressemblent à
« manifest unknown » ou « no matching manifest », selon le client et le registre.
Schéma courant en 2026 : des équipes construisent sur des laptops Arm, poussent « latest », puis les nœuds de prod (amd64) pullent et échouent. Personne
n’a changé le code. La réalité a changé d’architecture.
Pourquoi les proxys et miroirs aggravent le problème
Les caches de registre (pull-through caches, miroirs d’entreprise, gestionnaires d’artifacts) peuvent mettre en cache les tags, les manifestes, ou
les blobs avec des TTL différents. Si un tag est mis à jour en amont mais que le cache est périmé — ou pire, partiellement mis à jour — vous
pouvez obtenir « manifest unknown » du cache même si l’amont est OK. Ou vous pouvez l’obtenir de l’amont parce que vous tirez depuis l’espace de noms du cache par erreur.
Faits intéressants et courte histoire (car le passé est toujours d’astreinte)
- Fait 1 : L’API HTTP Docker Registry v2 (celle que tout le monde utilise maintenant) a été introduite pour résoudre des problèmes d’échelle et de correction de la v1, incluant un meilleur adressage par contenu.
- Fait 2 : OCI (Open Container Initiative) a standardisé le format d’image et les specs de distribution après l’explosion de l’écosystème Docker, donc vous traitez souvent des « images OCI » même quand vous dites « image Docker ».
- Fait 3 : Une « manifest list » en termes Docker est essentiellement un « image index » OCI. Deux noms, mêmes maux opérationnels.
- Fait 4 : Les registres implémentent fréquemment la suppression et la garbage collection en étapes séparées ; vous pouvez supprimer une référence de tag sans immédiatement supprimer les blobs, ou enlever des blobs plus tard et casser d’anciennes références.
- Fait 5 : Certains registres retournent volontairement 404/« unknown » pour des clients non autorisés afin d’éviter de divulguer quels dépôts/tags existent.
- Fait 6 : Les images multi-arch sont devenues mainstream quand les serveurs Arm et les machines de développeurs Arm sont devenus courants ; avant cela, beaucoup d’équipes ne voyaient jamais de manifest lists au quotidien.
- Fait 7 : Kubernetes ne « comprend » pas les tags au-delà de les transmettre au runtime ; quand les tags dérivent, K8s déploie fidèlement votre chaos à l’échelle.
- Fait 8 : Le digest que vous voyez sous
RepoDigestslocalement est lié à la référence du registre ; la même image locale peut avoir plusieurs repo digests si elle a été pullée depuis plusieurs registres/tags au fil du temps. - Fait 9 : BuildKit et
buildxont rendu les builds multi-plateforme accessibles, mais ont aussi facilité les pushes partiels si vous n’utilisez pas les bons flags.
Guide de diagnostic rapide
L’objectif est la vitesse : confirmer si vous avez affaire à (a) mauvais nom/tag, (b) plateforme manquante en multi-arch, (c) bizarrerie d’auth/miroir,
ou (d) rétention/suppression côté registre.
Première étape : confirmer la référence exacte que votre système tente de pull
- Copiez la référence d’image complète depuis les logs : hôte du registre, chemin du repo, tag ou digest.
- Vérifiez les erreurs invisibles : mauvais projet, namespace manquant, faute de frappe dans le tag, majuscule accidentelle.
Deuxième étape : tester le pull depuis un environnement propre (sans credentials cachés, sans image en cache)
- Essayez
docker pulldepuis un runner jetable ou une station avec un réseau connu bon. - Si ça marche là mais pas sur les nœuds, suspectez le proxy/miroir, des différences d’auth, ou un cache périmé.
Troisième étape : résoudre le tag en digest puis puller par digest
- Si le pull par digest fonctionne, votre problème est la dérive ou la suppression du tag.
- Si le pull par digest échoue avec « unknown », le manifeste a disparu (ou vous êtes dans le mauvais registre).
Quatrième étape : vérifier le matching multi-arch / plateforme
- Inspectez avec
docker buildx imagetools inspect. - Si la plateforme requise n’est pas listée, rebuild/push correctement.
Cinquième étape : confirmer l’état côté registre et les politiques de rétention
- Lister les tags si supporté ; inspecter les logs d’audit si vous en avez.
- Recherchez des jobs de nettoyage automatisés, des règles de purge « non taggés », ou une rétention temporelle qui a mangé votre tag.
Idée paraphrasée de Werner Vogels (CTO d’Amazon) : Vous le construisez, vous le faites tourner.
Si vous publiez des images, vous êtes responsable de la façon dont elles sont référencées et conservées.
Tâches pratiques : commandes, sorties et décisions (12+)
Ce sont des actions d’opérateur réelles : exécuter une commande, interpréter la sortie, décider de la prochaine action. Gardez-les dans votre runbook.
Task 1: Reproduire l’échec exactement (et ne pas « simplifier » le nom de l’image)
cr0x@server:~$ docker pull registry.example.com/payments/api:1.8.4
Error response from daemon: manifest for registry.example.com/payments/api:1.8.4 not found: manifest unknown: manifest unknown
Ce que cela signifie : Le endpoint du registre a répondu, et il n’a pas de manifeste pour ce repo+tag.
Décision : Vérifiez la précision de la référence, puis contrôlez si le tag existe et sur quoi il pointe.
Task 2: Vérifier une faute de frappe de tag en inspectant les tags proches (lorsque supporté)
cr0x@server:~$ curl -s -u "$REG_USER:$REG_PASS" https://registry.example.com/v2/payments/api/tags/list | jq .
{
"name": "payments/api",
"tags": [
"1.8.3",
"1.8.4-hotfix1",
"1.9.0",
"latest"
]
}
Ce que cela signifie : 1.8.4 n’existe pas, mais 1.8.4-hotfix1 existe.
Décision : Corrigez la référence de déploiement. Si vous attendiez 1.8.4, découvrez pourquoi il n’a jamais été poussé.
Task 3: Résoudre un tag en digest (côté client) avec buildx imagetools
cr0x@server:~$ docker buildx imagetools inspect registry.example.com/payments/api:1.9.0
Name: registry.example.com/payments/api:1.9.0
MediaType: application/vnd.oci.image.index.v1+json
Digest: sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
Manifests:
Name: registry.example.com/payments/api:1.9.0@sha256:aa1c... (linux/amd64)
Name: registry.example.com/payments/api:1.9.0@sha256:bb2d... (linux/arm64)
Ce que cela signifie : Le tag existe et pointe vers un index multi-arch avec deux plateformes.
Décision : Si vos nœuds sont amd64, le pull devrait marcher—à moins qu’un proxy interfère ou que l’auth diffère.
Task 4: Pull par digest pour éliminer la dérive de tag
cr0x@server:~$ docker pull registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
4c3b7d6b2a6d: Pulling from payments/api
Digest: sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
Status: Downloaded newer image for registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
Ce que cela signifie : Le contenu existe ; le problème est séparé et lié au tag.
Décision : Mettez à jour les manifests/déploiements pour épingler par digest en production, ou corrigez votre processus de promotion.
Task 5: Confirmer ce que vous avez réellement localement (RepoTags vs RepoDigests)
cr0x@server:~$ docker image inspect registry.example.com/payments/api:1.9.0 --format '{{json .RepoTags}} {{json .RepoDigests}}'
["registry.example.com/payments/api:1.9.0"] ["registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7"]
Ce que cela signifie : Vous avez à la fois le tag mutable et la référence digest immuable.
Décision : Utilisez le digest dans les lockfiles, valeurs Helm et manifests Kubernetes pour la production.
Task 6: Vérifier rapidement un mismatch de plateforme
cr0x@server:~$ uname -m
aarch64
Ce que cela signifie : Vous êtes sur Arm64.
Décision : Si l’image n’a que des manifestes amd64, vous aurez « no matching manifest » ou des variantes « unknown ». Inspectez l’index et rebuild en multi-arch.
Task 7: Inspecter la manifest list et vérifier que la plateforme nécessaire existe
cr0x@server:~$ docker buildx imagetools inspect registry.example.com/analytics/worker:2.1.0
Name: registry.example.com/analytics/worker:2.1.0
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
Digest: sha256:9b7f...
Manifests:
Name: registry.example.com/analytics/worker:2.1.0@sha256:1c2a... (linux/amd64)
Ce que cela signifie : Seul amd64 est publié.
Décision : Rebuild/push arm64, ou forcer l’ordonnancement sur des nœuds amd64, ou arrêter de prétendre que « ça marche sur mon laptop » est une stratégie plateforme.
Task 8: Prouver que vous parlez bien au registre que vous croyez
cr0x@server:~$ docker info | sed -n '1,25p'
Client:
Version: 26.1.0
Context: default
Debug Mode: false
Server:
Containers: 12
Running: 3
Paused: 0
Stopped: 9
Registry Mirrors:
https://mirror.corp.local
Live Restore Enabled: false
Ce que cela signifie : Un miroir de registre est configuré.
Décision : Si les pulls échouent seulement sur certains hôtes, comparez les paramètres de miroir ; essayez de contourner le miroir pour diagnostiquer.
Task 9: Contourner un miroir de registre (temporaire) pour confirmer sa staleness
cr0x@server:~$ sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak
cr0x@server:~$ sudo jq 'del(.["registry-mirrors"])' /etc/docker/daemon.json.bak | sudo tee /etc/docker/daemon.json >/dev/null
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ docker pull registry.example.com/payments/api:1.9.0
1.9.0: Pulling from payments/api
Digest: sha256:4c3b...
Status: Downloaded newer image for registry.example.com/payments/api:1.9.0
Ce que cela signifie : Le miroir était le problème (cache périmé, chemin bloqué, mismatch d’auth).
Décision : Corrigez ou évincez le cache du miroir ; ne laissez pas les nœuds contourner les miroirs « temporairement » sauf si vous aimez les surprises sur la facture d’egress.
Task 10: Vérifier les events Kubernetes quand l’erreur apparaît dans les clusters
cr0x@server:~$ kubectl -n payments describe pod api-7f95b6d8dd-4n2qg | sed -n '/Events:/,$p'
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 2m50s kubelet Pulling image "registry.example.com/payments/api:1.8.4"
Warning Failed 2m49s kubelet Failed to pull image "registry.example.com/payments/api:1.8.4": rpc error: code = Unknown desc = failed to resolve reference "registry.example.com/payments/api:1.8.4": not found
Warning Failed 2m49s kubelet Error: ErrImagePull
Warning BackOff 2m35s kubelet Back-off pulling image "registry.example.com/payments/api:1.8.4"
Warning Failed 2m35s kubelet Error: ImagePullBackOff
Ce que cela signifie : Le kubelet/runtime n’a pas pu résoudre la référence. Ceci est cohérent avec un tag manquant, des problèmes d’auth/miroir, ou un mismatch de plateforme.
Décision : Validez la référence d’image et les credentials utilisés par le nœud, pas par votre laptop.
Task 11: Vérifier le runtime du nœud et sa vue de la référence d’image
cr0x@server:~$ kubectl get node ip-10-1-2-3 -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}{"\n"}'
containerd://1.7.18
Ce que cela signifie : Vous utilisez containerd, pas Docker Engine.
Décision : Utilisez les outils crictl ou containerd sur le nœud pour un debug de pull plus profond ; le CLI Docker seul peut induire en erreur.
Task 12: Confirmer que l’imagePullSecret est présente et correcte (les échecs d’auth peuvent se déguiser)
cr0x@server:~$ kubectl -n payments get secret regcred -o jsonpath='{.type}{"\n"}'
kubernetes.io/dockerconfigjson
Ce que cela signifie : Le secret existe et est du bon type.
Décision : Si ça échoue toujours, décodez-le et confirmez qu’il pointe vers le même hôte de registre que celui d’où vous pull.
Task 13: Décoder le dockerconfigjson et vérifier que le hostname du registre correspond
cr0x@server:~$ kubectl -n payments get secret regcred -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq -r '.auths | keys[]'
https://registry.example.com
https://registry-old.example.com
Ce que cela signifie : Plusieurs entrées d’auth existent, y compris un ancien endpoint.
Décision : Supprimez les entrées obsolètes ; assurez-vous que le hostname exact dans la référence d’image possède des credentials.
Task 14: Utiliser skopeo pour inspecter à distance sans pull (idéal pour les runners CI)
cr0x@server:~$ skopeo inspect --creds "$REG_USER:$REG_PASS" docker://registry.example.com/payments/api:1.9.0 | jq -r '.Digest,.Architecture,.Os'
sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
amd64
linux
Ce que cela signifie : Le tag se résout, et vous pouvez voir le digest et l’architecture principale depuis le manifeste distant.
Décision : Si skopeo le voit mais que vos nœuds non, suspectez le chemin réseau, le miroir, ou les credentials du nœud.
Task 15: Inspecter directement les réponses du registre (confirmer 404 vs 401)
cr0x@server:~$ curl -s -o /dev/null -w "%{http_code}\n" -u "$REG_USER:$REG_PASS" \
-H 'Accept: application/vnd.oci.image.manifest.v1+json' \
https://registry.example.com/v2/payments/api/manifests/1.8.4
404
Ce que cela signifie : Le registre retourne 404 pour le manifeste de ce tag. C’est cohérent avec « le tag n’existe pas » ou « supprimé ».
Décision : Arrêtez de déboguer les nœuds. Corrigez la référence ou restaurez le tag/manifeste dans le registre.
Task 16: Prouver qu’une politique de rétention a supprimé le tag (chercher les images « untagged »)
cr0x@server:~$ skopeo inspect --creds "$REG_USER:$REG_PASS" docker://registry.example.com/payments/api:1.8.4-hotfix1 | jq -r '.Created,.Digest'
2026-01-02T17:11:09Z
sha256:11aa22bb33cc44dd55ee66ff77889900aabbccddeeff00112233445566778899
Ce que cela signifie : Une build similaire existe avec un tag différent et un temps de création récent, suggérant que votre tag attendu n’a jamais été promu ou a été supprimé.
Décision : Décidez si vous redéployez avec le tag/digest correct, ou republiez le tag manquant dans le cadre d’une release contrôlée.
Task 17: Pousser correctement des images multi-arch (éviter les pushes partiels)
cr0x@server:~$ docker buildx build --platform linux/amd64,linux/arm64 \
-t registry.example.com/analytics/worker:2.1.0 \
-t registry.example.com/analytics/worker:2.1 \
--push .
[+] Building 214.3s (31/31) FINISHED
=> pushing manifest for registry.example.com/analytics/worker:2.1.0
=> pushing manifest for registry.example.com/analytics/worker:2.1
Ce que cela signifie : Vous avez poussé un index multi-arch propre. Le tag devrait se résoudre pour les deux plateformes.
Décision : Retestez avec imagetools inspect. Si une seule plateforme apparaît, votre builder n’avait probablement pas QEMU/binfmt configuré ou le build a échoué pour une plateforme.
Task 18: Épingler dans Kubernetes par digest (le geste « arrêter l’hémorragie »)
cr0x@server:~$ kubectl -n payments set image deploy/api api=registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
deployment.apps/api image updated
Ce que cela signifie : Le déploiement référence maintenant un contenu immuable.
Décision : Faites cela pour les workloads de production. Gardez les tags pour les workflows humains, mais déployez par digest.
Trois mini-récits corporatifs depuis le terrain
Mini-récit 1 : Incident causé par une mauvaise hypothèse (les tags sont des versions)
Une fintech de taille moyenne avait une règle « simple » : staging utilisait :latest, la production utilisait un tag semver
comme :2.6.1. Ça paraissait mature. Ça avait l’air mature dans PowerPoint. Puis une release mineure est devenue un incident du vendredi soir.
L’ingénieur de release a rebuildé 2.6.1 pour inclure une mise à jour de bundle CA de dernière minute. Au lieu de couper
2.6.2, il a retaggé et pushé 2.6.1 à nouveau. Personne n’a pensé que cela avait de l’importance. « Même code », disaient-ils.
Leur registre autorisait l’écrasement de tag, et personne n’avait de politiques l’empêchant.
La production était une mise à jour progressive à travers plusieurs clusters. Certains nœuds ont pullé le 2.6.1 original. Certains ont pullé le
nouveau 2.6.1. Les migrations de l’app se sont exécutées une fois, mais le comportement différait légèrement à cause de changements dans les defaults TLS.
La moitié de la flotte parlait à une dépendance en amont ; l’autre moitié échouait. L’équipe d’astreinte a passé deux heures à enquêter sur des échecs « aléatoires »
jusqu’à ce que quelqu’un compare les RepoDigests entre nœuds.
La correction fut ennuyeuse et efficace : les déploiements de production ont épinglé les digests, et le processus de release a traité les tags comme
immuables. Si vous avez besoin d’une nouvelle build, vous coupez un nouveau tag. Pas d’exception. Le changement culturel a été plus grand que le changement technique :
« version » signifiait maintenant « artefact soutenu par digest », pas « chaîne ressemblant à une version ».
Mini-récit 2 : L’optimisation qui s’est retournée contre eux (cleanup agressif)
Dans une entreprise SaaS, la facture de stockage du registre contournait. Quelqu’un a proposé une politique de nettoyage :
supprimer les manifestes non taggés vieux de plus de 14 jours, et garbage-collecter agressivement les blobs pour récupérer de l’espace. Ça a été vendu comme
une optimisation de coût sans inconvénient. Ça aurait dû être votre premier indice.
Leur stratégie de déploiement utilisait la « promotion par retag » : builder :commit-abc123, le tester, puis retagger le même
digest en :prod. Mais le système CI créait parfois des tags intermédiaires puis les supprimait. Pendant un certain temps,
le manifeste était temporairement « untagged » avant d’être retaggé pour la promotion.
Le job de cleanup a tourné pendant cette fenêtre. Il a supprimé des manifestes qu’il jugeait non taggés et éligibles. Plus tard, la promotion a essayé
de retagger un digest qui n’avait plus de manifeste. Résultat : manifest unknown pour :prod au milieu d’un déploiement. C’est là que vous apprenez que votre définition de « non taggé » n’est pas la même que « inutilisé ».
Ils ont corrigé en séparant les responsabilités : pas de suppression des contenus non taggés récents, promotion effectuée comme opération atomique quand c’était supporté, et la fenêtre de nettoyage évitait les heures métiers. Aussi : l’équipe registre a commencé à traiter le « stockage » comme une dépendance de fiabilité, pas une ligne budgétaire à presser avec des cron jobs.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise (epinglage par digest + attestations)
Une entreprise de santé régulée avait une règle : chaque déploiement de production doit référencer les images par digest, et le digest doit
être enregistré dans le ticket de changement. Les gens grognaient. C’était bureaucratique. Ça ralentissait le « juste shiper », ce qui est
précisément la raison pour laquelle cela fonctionnait.
Un jour, leur fournisseur de registre a eu un délai de réplication interne entre régions. Les tags étaient visibles dans une région avant
que les manifestes sous-jacents ne soient entièrement répliqués dans une autre. Plusieurs équipes ont vu des manifest unknown
intermittents lors des pulls dans leur environnement de reprise après sinistre.
L’équipe d’astreinte n’a pas débattu si :3.4.7 était « la bonne version ». Ils avaient déjà le digest de la dernière release approuvée.
Ils ont pullé par digest depuis la région où il existait, l’ont mirrorié en interne, et ont mis à jour les manifests DR pour pointer vers la copie interne par digest. Les systèmes ont récupéré avec une ambiguïté minimale.
Ce n’était pas glamour. Pas d’héroïsme. Juste une traçabilité « ce digest est ce que nous exécutons », ce qui a transformé un problème vague de registre en un problème clair de réplication. La conformité a posé des questions plus tard ; les réponses étaient déjà écrites.
Erreurs courantes : symptômes → cause racine → correction
1) Symptom : « manifest unknown » pour un tag qui « existe certainement »
- Cause racine : Vous tirez depuis un hôte/namespace de registre différent de celui que vous avez vérifié. Les miroirs et les dépôts aux noms similaires adorent ce tour.
- Correction : Comparez la référence d’image complète dans les logs. Vérifiez les miroirs via
docker info. Résolvez viaskopeo inspectcontre l’hôte exact.
2) Symptom : Ça marche sur le laptop, échoue sur les nœuds
- Cause racine : Mismatch de plateforme (arm64 vs amd64) ou les credentials du runtime des nœuds diffèrent de ceux du développeur.
- Correction : Vérifiez
uname -msur les nœuds ; inspectez les plateformes de la manifest list ; vérifiezimagePullSecretset les IAM/credential helpers des nœuds.
3) Symptom : « manifest unknown » juste après un push
- Cause racine : Cohérence éventuelle/délai de réplication, ou vous avez poussé dans une région et tirez depuis une autre, ou cache/miroir périmé.
- Correction : Pull depuis le même endpoint où vous avez poussé ; contournez les miroirs ; réessayez avec backoff ; épinglez le digest après confirmation.
4) Symptom : Tag disparu du jour au lendemain
- Cause racine : Politique de rétention a supprimé tags/manifestes, ou quelqu’un a lancé la GC du registre sans comprendre les dépendances.
- Correction : Ajustez les politiques de rétention ; protégez les tags de release ; gardez un miroir interne « golden » ; stockez les digests dans les métadonnées de release.
5) Symptom : Kubernetes affiche not found mais l’UI du registre montre le tag
- Cause racine : L’UI peut afficher des métadonnées de tag depuis un backend différent ou une vue en cache ; le kubelet tire depuis un endpoint différent ou via un proxy.
- Correction : Pull depuis le chemin réseau du nœud ; vérifiez les configs du daemon du container runtime ; utilisez des appels API directs depuis le nœud.
6) Symptom : Une seule architecture fonctionne après un « push multi-arch »
- Cause racine : Vous n’avez poussé qu’un seul manifeste plateforme, ou vous avez poussé un index sans la plateforme désirée à cause d’un échec build/QEMU manquant.
- Correction : Utilisez
docker buildx build --platform ... --pushet vérifiez avecimagetools inspectavant d’étiqueter comme release.
7) Symptom : Des nœuds aléatoires échouent, d’autres réussissent
- Cause racine : Architectures mélangées sur les nœuds, configs de miroirs mélangées, ou credentials mélangés (certains nœuds voient des tags privés, d’autres non).
- Correction : Standardisez le bootstrap des nœuds ; auditez les configs du daemon ; assurez la cohérence des secrets/fournisseurs de credentials entre pools de nœuds.
Blague n°2 : « manifest unknown » est la manière polie du registre de dire « je ne sais pas de quoi vous parlez », ce qui est aussi ce que ressentent les auditeurs à propos de « latest ».
Checklists / plan étape par étape
Étape par étape : arrêter d’abord la panne
- Capturer la référence exacte en échec depuis les logs (y compris l’hôte du registre et le namespace).
- Tenter un pull direct depuis un hôte propre sur le même chemin réseau que les nœuds en échec.
- Résoudre en digest en utilisant
docker buildx imagetools inspectouskopeo inspect. - Déployer par digest (temporaire ou permanent). Cela évite la dérive de tag et vous achète du temps.
- Si le digest manque aussi, localisez le dernier digest connu bon dans les logs CI, SBOM/attestations, ou l’historique des déploiements.
- Si mismatch multi-arch, planifiez sur des nœuds compatibles ou publiez immédiatement la plateforme manquante.
Étape par étape : corriger le système pour que ça ne se reproduise pas
- Rendre les tags de release immuables (politique). Si votre registre le supporte, appliquez-le. Sinon, imposez-le via des gardes CI.
- Stocker les digests comme métadonnées de release (dans Git, métadonnées d’artifact, manifests de déploiement, enregistrements de changement).
- Utiliser les tags pour les humains, les digests pour les machines. La promotion peut toujours utiliser des tags, mais la production doit exécuter des digests.
- Vérifier les outputs multi-arch en CI avant de marquer une build comme publiable.
- Aligner la rétention avec la réalité des releases : protéger les tags de release, retarder la suppression des non-taggés, et ne pas GC agressivement sans comprendre les patterns de références.
- Standardiser la configuration des miroirs et les monitorer comme une dépendance. S’ils peuvent renvoyer des manifestes périmés, ils peuvent provoquer des pannes.
Checklist : avant d’accuser Docker
- Le tag est-il orthographié correctement et dans le bon chemin de dépôt ?
- Tirez-vous depuis le même endpoint de registre que celui où votre CI pousse ?
- Êtes-vous derrière un miroir, et est-il cohérent sur tous les nœuds ?
- La plateforme requise est-elle présente dans la manifest list ?
- La référence est-elle épinglée par digest en production ?
- La rétention/GC a-t-elle pu supprimer ou orpheliner le manifeste ?
- Les credentials des nœuds correspondent-ils à ceux de votre CI/machine dev ?
FAQ
1) Est-ce que « manifest unknown » signifie toujours un tag manquant ?
Non. C’est « manifeste manquant pour la référence que vous avez demandée ». Cela peut être un tag manquant, un digest supprimé, un mauvais
chemin de dépôt, ou une situation d’auth/miroir qui cache l’existence.
2) Pourquoi Docker dit parfois « manifest unknown » au lieu de « unauthorized » ?
Certains registres renvoient volontairement 404/unknown aux clients non autorisés pour éviter de révéler quels dépôts/tags existent. Vérifiez toujours
les credentials depuis la même machine/runtime qui échoue.
3) Quelle est la différence entre RepoTags et RepoDigests ?
RepoTags sont les noms que vous avez utilisés pour référencer une image localement (mutables). RepoDigests sont
des références de contenu supportées par le registre (immuables par contenu). Utilisez les digests pour les déploiements.
4) Si les digests sont immuables, pourquoi je vois encore des échecs en pullant par digest ?
Parce que l’immuabilité n’assure pas la disponibilité. Si le registre a supprimé le manifeste (ou si vous interrogez un registre/région différente), le digest peut être « unknown ». Immobile n’est pas immortel.
5) Devons-nous arrêter complètement d’utiliser les tags ?
Non. Les tags sont utiles pour les humains et les workflows : « ceci est release-2.1 », « ceci est prod », « ceci est canary ». Ne laissez juste pas
la correction en production dépendre d’un pointeur mutable. Déployez par digest, étiquetez par tag.
6) Comment m’assurer que les images multi-arch incluent réellement amd64 et arm64 ?
Construisez avec docker buildx build --platform ... --push, puis vérifiez avec
docker buildx imagetools inspect. Faites-en une porte CI, pas un espoir.
7) Pourquoi ça marche quand je contourne le miroir de registre ?
Votre miroir est périmé, mal configuré, ou n’a pas de credentials pour des dépôts privés. Les miroirs sont des systèmes, pas de la poussière magique de performances. Surveillez-les, versionnez leurs configs, et testez le comportement de basculement.
8) Kubernetes affiche « not found », mais je peux pull manuellement depuis mon laptop. Pourquoi ?
Des credentials différents, un DNS différent, un proxy/miroir différent, une architecture différente. Les nœuds Kubernetes pullent en tant que runtime nœud, pas en tant que vous. Reproduisez depuis le chemin réseau et le contexte runtime du nœud.
9) Quelle est la « réparation » la plus rapide pendant un incident ?
Épinglez le déploiement à un digest connu bon. Cela enlève immédiatement l’ambiguïté du tag. Ensuite, vous pouvez enquêter pourquoi le tag n’a pas résolu sans bloquer la production.
10) Le retagging est-il une stratégie de promotion valide ?
Oui, si vous le faites avec discipline : les tags qui représentent des environnements peuvent bouger, mais l’artefact de release doit être enregistré
par digest. Protégez aussi les tags de release contre l’écrasement, sinon vous finirez par expédier deux « mêmes versions » différentes.
Conclusion : quoi changer lundi
« manifest unknown » n’est pas une malédiction mystique de Docker. C’est un échec de lookup de contenu, généralement causé par des humains qui utilisent les tags comme
identités, ou par des systèmes qui traitent la suppression et le caching comme du ménage sans conséquence.
Prochaines étapes qui réduisent réellement les incidents :
- Déployer par digest en production. Gardez les tags, mais arrêtez de compter sur eux pour la correction.
- Rendre les tags de release immuables par politique et application.
- Valider les images multi-arch en CI et échouer les builds qui ne publient pas les plateformes requises.
- Auditer les miroirs et les politiques de rétention comme s’il s’agissait de dépendances de production — parce que ça l’est.
- Noter le digest au moment de la release. Le futur-vous appréciera de ne pas deviner.