Implementas. Funcionó ayer. Hoy tu pipeline falla con manifest unknown, y el único “cambio”
es que alguien actualizó “latest” en algún sitio porque “está bien, es solo una etiqueta.” Ahora producción está atascada en
ImagePullBackOff, tu canal de incidentes está en llamas, y estás a punto de aprender la diferencia entre una
etiqueta amigable para humanos y un puntero criptográfico.
La buena noticia: manifest unknown normalmente no es misterioso. Es el registro diciendo “no encuentro el
manifiesto que solicitaste.” La mala noticia: lo pediste de una forma que es fácil equivocarse. Hagámoslo aburrido de nuevo.
Qué significa realmente “manifest unknown”
En el mundo Docker/OCI, una “imagen” no es un único blob. Es un manifiesto que referencia capas y configuración, almacenado
en un registro. Cuando ejecutas docker pull repo:tag, tu cliente le pide al registro el manifiesto
correspondiente a esa referencia.
“manifest unknown” es la forma del registro de decir: “No tengo un manifiesto para ese nombre+referencia.”
Esa referencia puede ser:
- Una etiqueta (
repo:1.2.3) - Un digest (
repo@sha256:…)
Si el registro no puede resolver la etiqueta a un manifiesto, o no encuentra el digest, obtienes este error. No es (por lo general)
un timeout de red. No es un error de permisos. Es una falla de búsqueda.
Causas típicas en el mundo real:
- Esa etiqueta nunca existió en ese registro (error tipográfico, proyecto equivocado, región equivocada).
- Esa etiqueta existió, pero fue eliminada o movida (política de retención, recolección de basura, limpieza manual).
- Pusiste una imagen multi-arch incorrectamente, así que la etiqueta apunta a algo distinto de lo que el cliente espera.
- Estás extrayendo desde el endpoint de registro incorrecto (proxy/cache/espejo desincronizado).
- La autorización es extraña y el registro devuelve deliberadamente “unknown” para evitar filtrar qué existe.
Un principio operativo para tatuarte por dentro de las cejas:
las etiquetas son punteros, no identidades. Si las tratas como identidades, tu pager te tratará como un hobby.
Etiquetas vs digests: la verdad y las mentiras
Etiquetas: amigables, mutables, fáciles de romper
Una etiqueta Docker es una cadena asociada a un manifiesto en un repositorio. El registro almacena un mapeo:
tag → manifest. Eso es todo. No hay promesa de que el mapeo sea estable. No hay promesa de que la
etiqueta no se sobrescriba. No hay promesa de que tu CI/CD no compita con otro push.
Las etiquetas son excelentes para humanos. Son útiles en flujos como “promover 1.2.3 a prod retagueándolo como prod.”
Son terribles como el único control de la cadena de suministro en producción.
Digests: aburridos, inmutables, lo único que puedes demostrar
Un digest (como sha256:...) identifica contenido específico. Cuando extraes por digest, estás pidiendo un
manifiesto exacto por su hash. Ese hash cambia si el contenido cambia. Por tanto el digest es efectivamente inmutable.
En términos prácticos:
- Pull por etiqueta: “dame lo que el registro actualmente piensa que es
1.2.3.” - Pull por digest: “dame este manifiesto exacto, sin sustituciones.”
Cuando tu incidente es “funcionó en staging pero no en producción,” lo primero que busco es deriva de etiquetas. Lo segundo es deriva multi-arch. Lo tercero es si alguien “optimizó” el trabajo de limpieza del registro.
Broma #1: Las etiquetas son como las notas en la nevera de la oficina—útiles hasta que alguien decide “esto parece viejo” y tira tu almuerzo.
¿Qué hash calcula realmente el digest?
Esto importa porque la gente se confunde sobre “digest de capa” vs “digest de manifiesto”:
- Digest de manifiesto: hash del documento JSON del manifiesto (referencia capas/config). Extraer por digest apunta a esto.
- Digests de capas: hashes de cada blob de capa comprimida. Estos se comparten entre imágenes.
- Digest de config: hash del JSON de configuración de la imagen (env, entrypoint, history, rootfs diff IDs, etc.).
“manifest unknown” trata sobre el manifiesto (o la lista de manifiestos) que no se encuentra para tu referencia. No se queja de que
falte un blob de capa—cuando faltan capas verás errores distintos (a menudo 404 en blob, o fallos de descarga).
Manifiestos, listas de manifiestos y por qué el multi-arch lo complica
Las imágenes para una sola plataforma son directas: una etiqueta apunta a un manifiesto para una plataforma (por ejemplo linux/amd64). Las imágenes multi-arch agregan un nivel: la etiqueta apunta a una lista de manifiestos (OCI index / Docker manifest list),
que luego apunta a manifiestos por plataforma.
Tu cliente hace negociación de contenido con encabezados y elige una entrada que coincida con su plataforma (arquitectura, OS,
a veces variante). Si la lista de manifiestos no tiene la entrada para la plataforma, puedes ver errores que parecen
“manifest unknown” o “no matching manifest”, según el cliente y el registro.
Patrón común en 2026: equipos desarrollan en portátiles Arm, pushean “latest,” luego los nodos de producción (amd64) hacen pull y fallan. Nadie
cambió código. La realidad cambió de arquitectura.
Por qué los proxies y mirrors lo empeoran
Las cachés de registro (pull-through caches, mirrors corporativos, gestores de artefactos) pueden cachear etiquetas, manifiestos o
blobs con TTLs distintos. Si una etiqueta se actualiza upstream pero la caché está desactualizada—o peor, actualizada parcialmente—puedes obtener “manifest unknown” desde la caché aunque upstream esté bien. O puedes obtenerlo desde upstream porque estás
extrayendo desde el namespace de la caché por error.
Hechos interesantes y breve historia (porque el pasado aún está de guardia)
- Hecho 1: La API HTTP del Docker Registry v2 (la que usa todo el mundo actualmente) se introdujo para arreglar problemas de escalado y corrección de v1, incluyendo mejor direccionamiento por contenido.
- Hecho 2: OCI (Open Container Initiative) estandarizó el formato de imagen y las especificaciones de distribución después de la explosión temprana del ecosistema Docker, así que a menudo trabajas con “imágenes OCI” incluso cuando dices “imagen Docker.”
- Hecho 3: Una “manifest list” en términos Docker es esencialmente un “image index” de OCI. Dos nombres, los mismos dolores operativos.
- Hecho 4: Los registros frecuentemente implementan eliminación y recolección de basura como pasos separados; puedes borrar una etiqueta sin remover inmediatamente los blobs, o eliminar blobs más tarde y romper referencias antiguas.
- Hecho 5: Algunos registros intencionalmente devuelven 404/“unknown” a clientes no autorizados para evitar filtrar qué repositorios/etiquetas existen.
- Hecho 6: Las imágenes multi-arch se volvieron mainstream cuando los servidores y máquinas de desarrollador Arm se hicieron comunes; antes de eso, muchos equipos nunca vieron listas de manifiestos en el trabajo diario.
- Hecho 7: Kubernetes no “entiende etiquetas” más allá de pasárselas al runtime; cuando las etiquetas derivan, K8s despliega tu caos a escala con fidelidad.
- Hecho 8: El digest que ves como
RepoDigestslocalmente está ligado a la referencia del registro; la misma imagen local puede tener múltiples repo digests si se extrajo desde varios registros/etiquetas a lo largo del tiempo. - Hecho 9: BuildKit y
buildxhicieron accesibles las compilaciones multi-plataforma, pero también facilitaron pushear resultados parciales si no usas las banderas correctas.
Guion de diagnóstico rápido
El objetivo es velocidad: confirma si tratas con (a) nombre/etiqueta equivocados, (b) plataforma faltante en multi-arch, (c) cosas raras de auth/mirror,
o (d) retención/eliminación en el registro.
Primero: confirma la referencia exacta que tu sistema intenta extraer
- Copia la referencia completa de la imagen desde los logs: host del registro, ruta del repo, etiqueta o digest.
- Revisa por errores invisibles: proyecto equivocado, namespace faltante, error tipográfico en la etiqueta, mayúsculas accidentales.
Segundo: prueba el pull desde un entorno limpio (sin credenciales caché, sin imagen en caché)
- Intenta
docker pulldesde un runner descartable o una estación de trabajo con red conocida buena. - Si funciona allí pero no en los nodos, sospecha proxy/mirror, diferencias de auth, o caché desactualizada.
Tercero: resuelve la etiqueta a un digest y luego haz pull por digest
- Si extraer por digest funciona, tu problema es deriva de etiquetas o eliminación de etiquetas.
- Si el pull por digest falla con “unknown”, el manifiesto desapareció (o estás en el registro equivocado).
Cuarto: verifica el matching multi-arch / plataforma
- Inspecciona con
docker buildx imagetools inspect. - Si la plataforma necesaria no está listada, recompila/pusha correctamente.
Quinto: confirma el estado en el lado del registro y las políticas de retención
- Lista etiquetas si es soportado; inspecciona logs de auditoría si los tienes.
- Busca jobs de limpieza automáticos, reglas de purga de “sin etiqueta”, o retención basada en tiempo que se haya comido tu etiqueta.
Idea parafraseada de Werner Vogels (CTO de Amazon): Tú lo construyes, tú lo ejecutas.
Si publicas imágenes, eres responsable de cómo se referencian y retienen.
Tareas prácticas: comandos, salidas y decisiones (12+)
Estos son movimientos reales de operador: ejecuta un comando, interpreta la salida, decide la acción siguiente. Manténlos en tu runbook.
Task 1: Reproduce la falla exactamente (y no “simplifiques” el nombre de la imagen)
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
Qué significa: El endpoint del registro respondió, y no tiene un manifiesto para ese repo+tag.
Decisión: Verifica la corrección de la referencia, luego comprueba si la etiqueta existe y a qué apunta.
Task 2: Comprueba si es un error tipográfico en la etiqueta inspeccionando etiquetas cercanas (cuando sea soportado)
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"
]
}
Qué significa: 1.8.4 no existe, pero 1.8.4-hotfix1 sí.
Decisión: Corrige la referencia de despliegue. Si esperabas 1.8.4, averigua por qué nunca se empujó.
Task 3: Resuelve una etiqueta a un digest (lado cliente) con 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)
Qué significa: La etiqueta existe y apunta a un índice multi-arch con dos plataformas.
Decisión: Si tus nodos son amd64, la extracción debería funcionar—salvo que un proxy interfiera o la auth difiera.
Task 4: Extrae por digest para eliminar la deriva de etiquetas
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
Qué significa: El contenido existe; el problema es de etiquetas por separado.
Decisión: Actualiza manifiestos/despliegues para fijar por digest en prod, o arregla tu proceso de promoción.
Task 5: Confirma qué tienes realmente localmente (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"]
Qué significa: Tienes tanto la etiqueta mutable como la referencia inmutable por digest.
Decisión: Usa el digest en lockfiles, valores de Helm y manifiestos de Kubernetes para producción.
Task 6: Comprueba rápidamente el desajuste de plataforma
cr0x@server:~$ uname -m
aarch64
Qué significa: Estás en Arm64.
Decisión: Si la imagen solo tiene manifiestos amd64, obtendrás “no matching manifest” o variantes “unknown”. Inspecciona el índice y recompila como multi-arch.
Task 7: Inspecciona la lista de manifiestos y verifica que exista la plataforma que necesitas
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)
Qué significa: Solo amd64 está publicado.
Decisión: O recompilas/push arm64, o fuerzas scheduling en nodos amd64, o dejas de fingir que “funciona en mi laptop” es una estrategia de plataforma.
Task 8: Demuestra que estás hablando con el registro que crees
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
Qué significa: Hay un mirror de registro configurado.
Decisión: Si los pulls fallan solo en ciertos hosts, compara la configuración de mirrors; intenta evitar el mirror para diagnóstico.
Task 9: Evita temporalmente un mirror de registro para confirmar caché estancada
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
Qué significa: El mirror era el problema (caché obsoleta, ruta bloqueada, discrepancia de auth).
Decisión: Arregla o invalida la caché del mirror; no dejes nodos “temporalmente” evitando mirrors a menos que te gusten las sorpresas en la factura de egress.
Task 10: Revisa eventos de Kubernetes cuando el error aparece en 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
Qué significa: Kubelet/runtime no pudo resolver la referencia. Esto es consistente con etiqueta faltante, problemas de auth/mirror, o desajuste de plataforma.
Decisión: Valida la referencia de la imagen y las credenciales usadas por el nodo, no por tu laptop.
Task 11: Verifica el runtime del nodo y su vista de la referencia de imagen
cr0x@server:~$ kubectl get node ip-10-1-2-3 -o jsonpath='{.status.nodeInfo.containerRuntimeVersion}{"\n"}'
containerd://1.7.18
Qué significa: Estás usando containerd, no Docker Engine.
Decisión: Usa herramientas crictl o de containerd en el nodo para depuración más profunda del pull; el Docker CLI por sí solo puede engañarte.
Task 12: Confirma que el imagePullSecret esté presente y correcto (fallos de auth pueden disfrazarse)
cr0x@server:~$ kubectl -n payments get secret regcred -o jsonpath='{.type}{"\n"}'
kubernetes.io/dockerconfigjson
Qué significa: El secreto existe y es del tipo correcto.
Decisión: Si sigue fallando, decódificalo y confirma que apunte al mismo host de registro desde el que haces pull.
Task 13: Decodifica el dockerconfigjson y comprueba que el hostname del registro coincida
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
Qué significa: Existen múltiples entradas de auth, incluyendo un endpoint antiguo.
Decisión: Elimina entradas obsoletas; asegúrate de que el hostname exacto en la referencia de imagen tenga credenciales.
Task 14: Usa skopeo para inspeccionar remotamente sin extraer (ideal para 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
Qué significa: La etiqueta se resuelve, y puedes ver el digest y la arquitectura principal desde el manifiesto remoto.
Decisión: Si skopeo lo ve pero tus nodos no, sospecha ruta de red, mirror o credenciales del nodo.
Task 15: Inspecciona respuestas del registro directamente (confirma 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
Qué significa: El registro devuelve 404 para el manifiesto de esa etiqueta. Eso es consistente con “la etiqueta no existe” o “eliminada.”
Decisión: Deja de depurar nodos. Arregla la referencia o restaura la etiqueta/manifiesto en el registro.
Task 16: Demuestra que una política de retención borró la etiqueta (busca imágenes “sin etiqueta”)
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
Qué significa: Existe una compilación similar con otra etiqueta y un tiempo de creación reciente, lo que sugiere que la etiqueta esperada nunca se promovió o fue eliminada.
Decisión: Decide si volver a desplegar usando la etiqueta/digest correcto, o republicar la etiqueta faltante como parte de un lanzamiento controlado.
Task 17: Empuja correctamente imágenes multi-arch (evita pushes parciales)
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
Qué significa: Empujaste un índice multi-arch correcto. La etiqueta debería resolverse para ambas plataformas.
Decisión: Vuelve a probar con imagetools inspect. Si solo aparece una plataforma, tu builder probablemente no tenía QEMU/binfmt configurado o la compilación falló para una plataforma.
Task 18: Fija en Kubernetes por digest (la medida para “detener la hemorragia”)
cr0x@server:~$ kubectl -n payments set image deploy/api api=registry.example.com/payments/api@sha256:4c3b7d6b2a6d7f3e9c5b2d6c0a7c9b3a2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f7
deployment.apps/api image updated
Qué significa: El deployment ahora referencia contenido inmutable.
Decisión: Haz esto para cargas de trabajo de producción. Mantén etiquetas para flujos humanos, pero despliega por digest.
Tres micro-historias corporativas desde el frente
Micro-historia 1: El incidente causado por una suposición equivocada (las etiquetas son versiones)
Una fintech mediana tenía una regla “simple”: staging usaba :latest, producción usaba una etiqueta semver
como :2.6.1. Sonaba maduro. Parecía maduro en PowerPoint. Luego una release menor se convirtió en un incidente de viernes por la noche.
El release engineer reconstruyó 2.6.1 para incluir una actualización de CA de último minuto. En lugar de cortar
2.6.2, volvieron a etiquetar y pushearon 2.6.1 otra vez. Nadie pensó que importara. “Mismo código,” dijeron.
Su registro permitía sobrescribir etiquetas, y nadie tenía políticas que lo impidieran.
Producción fue una actualización rolling a través de múltiples clusters. Algunos nodos extrajeron el 2.6.1 original. Otros extrajeron el
nuevo 2.6.1. Las migraciones de la app se ejecutaron una vez, pero el comportamiento de la app difería ligeramente por los cambios en TLS.
Mitad de la flota se comunicó con una dependencia upstream; la otra mitad falló. El equipo on-call pasó dos horas investigando fallos “aleatorios”
hasta que alguien comparó los RepoDigests entre nodos.
La solución fue aburrida y efectiva: los despliegues de producción fijaron digests, y el proceso de release trató las etiquetas como
inmutables. Si necesitas una nueva compilación, cortas una nueva etiqueta. Sin excepciones. El cambio cultural fue mayor que el técnico:
“versión” ahora significaba “artefacto respaldado por digest,” no “cadena que parecía una versión.”
Micro-historia 2: La optimización que salió mal (limpieza agresiva)
En una SaaS empresarial, la factura de almacenamiento del registry subía. Alguien propuso una política de limpieza:
borrar manifiestos sin etiqueta mayores a 14 días, y garbage-collect agresivamente blobs para recuperar espacio. Se presentó como
una optimización de coste sin desventajas. Ese debió haber sido tu primer indicio.
Su estrategia de despliegue usaba “promoción por retag”: construir :commit-abc123, probarlo, luego retaguear el mismo
digest como :prod. Pero el sistema CI ocasionalmente creaba etiquetas intermedias y luego las eliminaba. Por un tiempo,
el manifiesto quedó temporalmente “sin etiqueta” antes de ser retagueado en la promoción.
El job de limpieza corrió durante esa ventana. Eliminó manifiestos que creyó sin etiqueta y elegibles. Más tarde, la promoción intentó
retaguear un digest que ya no tenía manifiesto. Resultado: manifest unknown para :prod en medio
de un despliegue. Así aprendes que tu definición de “sin etiqueta” no es la misma que “sin uso.”
Lo arreglaron separando responsabilidades: no borrar contenido sin etiqueta reciente, realizar la promoción como operación atómica
cuando sea soportado, y evitar la ventana de limpieza durante horas de negocio. Además: el equipo de registro empezó a tratar el “almacenamiento” como una
dependencia de fiabilidad, no como una línea presupuestaria para apretar con cron jobs.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día (pin por digest + atestaciones)
Una compañía sanitaria regulada tenía una regla: cada despliegue en producción debía referenciar imágenes por digest, y el digest debía
registrarse en el ticket de cambio. La gente se quejaba. Parecía burocrático. Frenaba el “solo súbelo”, que es
precisamente por qué funcionó.
Un día, su proveedor de registro tuvo un retraso de replicación interno entre regiones. Las etiquetas eran visibles en una región antes de
que los manifiestos subyacentes se replicaran completamente en otra. Varios equipos vieron intermitentes manifest unknown
durante pulls en su entorno de recuperación ante desastres.
El equipo on-call no debatió si :3.4.7 era “la versión correcta.” Ya tenían el digest de la última aprobación. Extrajeron por digest desde la
región donde existía, lo replicaron internamente y actualizaron los manifiestos de DR para apuntar a la copia interna por digest. Los sistemas se recuperaron con mínima ambigüedad.
No fue glamuroso. Nada heroico. Solo una pista de auditoría de “este digest es lo que ejecutamos,” que convirtió un problema borroso de registro
en un problema de replicación directo. Compliance hizo preguntas después; las respuestas ya estaban escritas.
Errores comunes: síntomas → causa raíz → corrección
1) Síntoma: “manifest unknown” para una etiqueta que “definitivamente existe”
- Causa raíz: Estás extrayendo desde un host/namespace de registro distinto al que verificaste. Los mirrors y repos con nombres similares adoran este truco.
- Corrección: Compara la referencia completa de la imagen en los logs. Verifica los mirrors con
docker info. Resuelve conskopeo inspectcontra el host exacto.
2) Síntoma: Funciona en el portátil, falla en los nodos
- Causa raíz: Desajuste de plataforma (arm64 vs amd64) o las credenciales del runtime del nodo difieren de las del desarrollador.
- Corrección: Comprueba
uname -men los nodos; inspecciona las plataformas de la lista de manifiestos; verificaimagePullSecretsy proveedores de credenciales/IAM del nodo.
3) Síntoma: “manifest unknown” justo después de un push
- Causa raíz: Consistencia eventual/retraso de replicación, o empujaste a una región y extrajiste de otra, o caché/mirror obsoleto.
- Corrección: Extrae desde el mismo endpoint al que empujaste; evita mirrors; reintenta con backoff; fija por digest tras confirmar.
4) Síntoma: La etiqueta desapareció de la noche a la mañana
- Causa raíz: La política de retención borró etiquetas/manifiestos, o alguien ejecutó GC del registro sin entender dependencias.
- Corrección: Ajusta políticas de retención; protege etiquetas de release; mantén un mirror interno “golden”; almacena digests en metadatos de release.
5) Síntoma: Kubernetes muestra not found pero la UI del registro muestra la etiqueta
- Causa raíz: La UI puede mostrar metadatos de etiquetas desde un backend diferente o una vista cacheada; kubelet está extrayendo desde un endpoint distinto o a través de un proxy.
- Corrección: Extrae desde la ruta de red del nodo; comprueba la config del daemon del contenedor para mirrors; usa llamadas directas a la API del registro desde el nodo.
6) Síntoma: Solo una arquitectura funciona después de un “push multi-arch”
- Causa raíz: Solo empujaste un manifiesto para una plataforma, o empujaste un índice sin la plataforma deseada debido a fallo de build/QEMU ausente.
- Corrección: Usa
docker buildx build --platform ... --pushy verifica conimagetools inspectantes de etiquetar como release.
7) Síntoma: Nodos aleatorios fallan, nodos aleatorios tienen éxito
- Causa raíz: Arquitecturas mixtas en los nodos, configuraciones de mirror mixtas, o credenciales mixtas (algunos nodos pueden ver tags privados, otros no).
- Corrección: Estandariza el bootstrap de nodos; audita configs del daemon; asegura que secretos/proveedores de credenciales sean consistentes entre pools de nodos.
Broma #2: “manifest unknown” es la forma cortés del registro de decir “no tengo idea de lo que hablas,” que también es cómo se sienten los auditores acerca de “latest.”
Listas de verificación / plan paso a paso
Paso a paso: detén el outage primero
- Captura la referencia exacta que falla desde los logs (incluyendo hostname y namespace del registro).
- Intenta un pull directo desde un host limpio en la misma ruta de red que los nodos que fallan.
- Resuelve a digest usando
docker buildx imagetools inspectoskopeo inspect. - Despliega por digest (temporal o permanente). Esto evita la deriva de etiquetas y te compra tiempo.
- Si el digest también falta, localiza el último digest conocido bueno en logs de CI, SBOM/atestaciones, o historial de despliegues previos.
- Si hay mismatch multi-arch, programa en nodos compatibles o publica inmediatamente la plataforma faltante.
Paso a paso: arregla el sistema para que no vuelva a pasar
- Haz las etiquetas de release inmutables (política). Si tu registro lo soporta, aplícalo. Si no, impónlo vía gates en CI.
- Almacena digests como metadatos de release (en Git, metadata de artefactos, manifiestos de despliegue, registros de cambio).
- Usa etiquetas para humanos, digests para máquinas. La promoción aún puede usar etiquetas, pero producción debe ejecutar digests.
- Verifica salidas multi-arch en CI antes de marcar un build como publicable.
- Alinea la retención con la realidad de releases: protege etiquetas de release, retrasa la eliminación de sin-etiqueta, y no ejecutes GC agresivo sin entender patrones de referencia.
- Estandariza la configuración de mirrors y móntoréalos como una dependencia. Si pueden devolver manifiestos obsoletos, pueden causar outages.
Checklist: antes de culpar a Docker
- ¿Está la etiqueta escrita correctamente y en la ruta de repositorio correcta?
- ¿Estás extrayendo desde el mismo endpoint del registro al que tu CI empuja?
- ¿Estás detrás de un mirror, y es consistente entre nodos?
- ¿Está la plataforma requerida presente en la lista de manifiestos?
- ¿Está la referencia fijada por digest para producción?
- ¿Podría la retención/GC haber eliminado u huérfano el manifiesto?
- ¿Coinciden las credenciales del nodo con las que usa tu CI/máquina de desarrollo?
Preguntas frecuentes
1) ¿“manifest unknown” siempre es una etiqueta faltante?
No. Es “manifiesto faltante para la referencia que solicitaste.” Eso puede ser una etiqueta faltante, un digest eliminado, una ruta de repositorio incorrecta, o una situación de auth/mirror que oculta la existencia.
2) ¿Por qué Docker a veces dice “manifest unknown” en lugar de “unauthorized”?
Algunos registros devuelven intencionalmente 404/unknown a clientes no autorizados para evitar filtrar qué repos/repos/etiquetas existen. Siempre verifica credenciales en la misma máquina/runtime que falla.
3) ¿Cuál es la diferencia entre RepoTags y RepoDigests?
RepoTags son los nombres que usaste para referenciar una imagen localmente (mutables). RepoDigests son
referencias al contenido respaldadas por el registro (inmutables por contenido). Usa digests para despliegues.
4) Si los digests son inmutables, ¿por qué sigo viendo fallos al extraer por digest?
Porque la inmutabilidad no garantiza disponibilidad. Si el registro borró el manifiesto (o estás consultando otro registro/otra región), el digest puede ser “unknown.” Inmutable no es inmortal.
5) ¿Deberíamos dejar de usar etiquetas por completo?
No. Las etiquetas son útiles para humanos y flujos: “esta es release-2.1,” “esta es prod,” “esta es canary.” Simplemente no permitas que la corrección en producción dependa de un puntero mutable. Despliega por digest, etiqueta para humanos.
6) ¿Cómo me aseguro de que las imágenes multi-arch incluyan amd64 y arm64?
Compila con docker buildx build --platform ... --push, luego verifica con
docker buildx imagetools inspect. Haz de la verificación una puerta en CI, no una esperanza.
7) ¿Por qué funciona cuando evito el mirror del registro?
Tu mirror está obsoleto, mal configurado, o no tiene credenciales para repositorios privados. Los mirrors son sistemas, no polvo mágico de rendimiento. Monitorízalos, versiona sus configs y prueba el comportamiento de failover.
8) Kubernetes muestra “not found,” pero puedo hacer pull manualmente desde mi laptop. ¿Por qué?
Credenciales diferentes, DNS diferente, proxy/mirror distinto, arquitectura distinta. Los nodos de Kubernetes extraen como el runtime del nodo, no como tú. Reproduce desde la ruta de red y el contexto runtime del nodo.
9) ¿Cuál es la “solución” más rápida durante un incidente?
Fija el deployment a un digest conocido bueno. Elimina la ambigüedad de la etiqueta inmediatamente. Luego investiga por qué la etiqueta no se resolvió sin mantener producción como rehén.
10) ¿Es retaggear una estrategia de promoción válida?
Sí, si se hace con disciplina: las etiquetas que representan entornos pueden moverse, pero el artefacto release debe registrarse
por digest. Además, protege las etiquetas de release contra sobrescritura, o acabarás enviando dos “mismas versiones” distintas.
Conclusión: qué cambiar el lunes
“manifest unknown” no es una maldición mística de Docker. Es una falla de búsqueda de contenido, usualmente causada por humanos que usan etiquetas como
identidades, o por sistemas que tratan eliminación y caching como tareas domésticas inofensivas.
Siguientes pasos que realmente reducen incidentes:
- Desplegar por digest en producción. Mantén etiquetas, pero deja de depender de ellas para la corrección.
- Haz las etiquetas de release inmutables mediante políticas y enforcement.
- Valida imágenes multi-arch en CI y falla builds que no publiquen las plataformas requeridas.
- Audita mirrors y políticas de retención como si fueran dependencias de producción—porque lo son.
- Escribe el digest en el momento del release. Tu yo futuro agradecerá no tener que adivinar.