ImagePullBackOff significa que Kubernetes intentó descargar tu imagen de contenedor pero falló. El pod espera, reintenta, y falla de nuevo en un bucle de backoff exponencial. Esta guía te muestra cómo diagnosticar y corregir las causas más comunes.

Síntoma

El estado del pod muestra ImagePullBackOff. Ejecutar kubectl describe pod revela:

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Pending
Events:
  Type     Reason             Age    Message
  ----     ------             ----   -------
  Normal   Pulling            2m15s  Pulling image "example:latest"
  Warning  Failed             2m12s  Failed to pull image "example:latest":
           rpc error: code = Unknown desc = Error response from daemon: manifest not found
  Warning  Failed             2m8s   Failed to pull image "example:latest": rpc error: code = Unknown desc = Error response from daemon: manifest not found
  Normal   BackOff            115s   Back-off pulling image "example:latest"

Por qué ocurre

Ordenado por probabilidad:

  1. Nombre de imagen o tag incorrecto — La imagen no existe en el registro. Typo en el nombre, el tag no existe, o la imagen nunca fue empujada.
  2. Registro privado sin imagePullSecrets — Kubernetes no puede autenticarse en el registro y es rechazado con «no autorizado» o «autenticación requerida».
  3. Límite de velocidad de Docker Hub alcanzado — Las descargas no autenticadas desde Docker Hub son limitadas. Después de 100 descargas por 6 horas, las nuevas descargas fallan.
  4. Arquitectura o SO incorrecto — La imagen está construida para una arquitectura de CPU diferente (por ejemplo, arm64 vs amd64) u OS, y el runtime no puede ejecutarla.
  5. El registro está caído, inalcanzable o bloqueado — Network policy, firewall, DNS, o el servidor del registro en sí está offline o mal configurado.
  6. Credenciales vencidas o inválidas — Las credenciales del registro en el Secret han cambiado o el token ha vencido.

Solución

1. Nombre de imagen o tag incorrecto

Verifica qué especificaste:

kubectl get pod <pod-name> -o yaml | grep -A 2 "image:"

Verifica que la imagen existe en el registro:

# Para Docker Hub
docker pull example:latest

# Para un registro privado
docker pull registry.example.com/example:latest

# Si la descarga falla localmente, también fallará en Kubernetes

Si la imagen no existe, empújala primero:

# Construir la imagen
docker build -t example:latest .

# Etiquetar para tu registro
docker tag example:latest registry.example.com/example:latest

# Empujar
docker push registry.example.com/example:latest

Luego actualiza tu Deployment para usar la imagen correcta:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: registry.example.com/example:v1.0.0    # Imagen correcta

Aplica y reintenta:

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/example-app

2. Registro privado sin imagePullSecrets

Si el registro es privado, Kubernetes necesita credenciales. Crea un Secret docker-registry:

kubectl create secret docker-registry myregistrycred \
  --docker-server=registry.example.com \
  --docker-username=myuser \
  --docker-password=mypass \
  --docker-email=me@example.com \
  --namespace=default

O desde un config de docker login:

# Si ya has iniciado sesión localmente, copia tus credenciales
kubectl create secret generic myregistrycred \
  --from-file=.dockerconfigjson=$HOME/.docker/config.json \
  --type=kubernetes.io/dockercfg

Verifica que el secret fue creado:

kubectl get secret myregistrycred -o yaml

Luego referenciarla en tu spec de pod:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app
spec:
  template:
    spec:
      imagePullSecrets:
      - name: myregistrycred
      containers:
      - name: app
        image: registry.example.com/example:latest

Alternativamente, agrega el secret a tu service account para que todos los pods en esa cuenta lo usen:

kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "myregistrycred"}]}'

Verifica:

kubectl get serviceaccount default -o yaml

Luego elimina y recrea el pod para descargar con el nuevo secret:

kubectl delete pod <pod-name>

3. Límite de velocidad de Docker Hub alcanzado

Si estás descargando sin autenticación desde Docker Hub, alcanzaste el límite de 100 descargas por 6 horas. Auténtica en su lugar:

Crea un Docker Hub Personal Access Token (o usa tu contraseña) y crea un secret:

kubectl create secret docker-registry dockerhubcred \
  --docker-server=docker.io \
  --docker-username=myusername \
  --docker-password=dckr_pat_XXXXXXX \
  --docker-email=me@example.com

Actualiza tu Deployment para referenciarlo:

imagePullSecrets:
- name: dockerhubcred

O agrégalo a la service account predeterminada:

kubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "dockerhubcred"}]}'

Para límites más altos, usa una cuenta de pago en Docker Hub o un registro privado. Elimina y recrea los pods para reintentar con las nuevas credenciales:

kubectl delete pod <pod-name>

4. Arquitectura u OS incorrecto

La imagen fue construida para una CPU u OS diferente. Verifica qué arquitectura tiene el nodo:

kubectl get nodes -o wide | grep -i arch
# O:
kubectl describe node <node-name> | grep "kubernetes.io/arch"

Verifica qué arquitectura soporta la imagen. Constrúyela para la arquitectura correcta usando Docker BuildX:

# Construir para múltiples arquitecturas
docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/example:latest \
  --push .

# O construir solo para tu objetivo
docker buildx build --platform linux/arm64 \
  -t registry.example.com/example:arm64 .

También puedes usar una imagen base con múltiples arquitecturas:

FROM node:20-alpine    # Ya soporta amd64, arm64, etc.
COPY . /app
WORKDIR /app
CMD ["node", "server.js"]

Si la imagen ya está construida para la arquitectura incorrecta, reconstruyela o usa una imagen base diferente.

5. El registro está caído, inalcanzable o bloqueado

Prueba la conectividad de red al registro:

# Desde el clúster
kubectl run debug --image=busybox -it --rm -- sh
# Dentro del contenedor:
wget https://registry.example.com
nc -zv registry.example.com 443

Verifica la resolución de DNS:

kubectl run debug --image=busybox -it --rm -- sh
# Dentro:
nslookup registry.example.com

Verifica políticas de red que podrían bloquear egreso:

kubectl get networkpolicy -A

Verifica las reglas de firewall del clúster. Si el registro está dentro del clúster (por ejemplo, una instancia privada de Harbor), asegúrate de:
– El pod del registro está en ejecución: kubectl get pod -n registry
– El servicio del registro es alcanzable: kubectl get svc -n registry

Espera a que el registro vuelva a estar online, luego elimina el pod para reintentar:

kubectl delete pod <pod-name>

6. Credenciales vencidas o inválidas

Si actualizaste credenciales en tu registro privado, actualiza el Secret de Kubernetes:

# Eliminar el secret antiguo
kubectl delete secret myregistrycred

# Crear uno nuevo con credenciales actualizadas
kubectl create secret docker-registry myregistrycred \
  --docker-server=registry.example.com \
  --docker-username=myuser \
  --docker-password=mynewpass \
  --docker-email=me@example.com

Forzar que los pods se re-autentiquen eliminándolos:

kubectl delete pod <pod-name>

Para autenticación basada en tokens (por ejemplo, tokens de ServiceAccount de Kubernetes usados por registros dentro del clúster), regenera el token y actualiza el Secret.

Cómo evitarlo

  1. Empuja la imagen antes de desplegar:

    docker build -t registry.example.com/app:v1.0.0 .
    docker push registry.example.com/app:v1.0.0
    # Luego despliega con ese tag exacto
    

  2. Usa tags específicos, no latest:

    image: registry.example.com/app:v1.2.3    # Bueno
    image: registry.example.com/app:latest    # Arriesgado
    

  3. Auténtica en registros privados:

    kubectl create secret docker-registry myregistrycred ...
    # Y úsalo en todos los Deployments
    

  4. Pre-descarga imágenes en nodos usando DaemonSets para imágenes usadas frecuentemente, reduciendo tiempo de descarga y fallos.

  5. Usa un mirror o cache de registro privado (por ejemplo, Harbor, Artifactory) para reducir dependencia en los límites de velocidad de Docker Hub.

  6. Prueba la descarga de imagen antes de hacer commit a Kubernetes:

    docker run --rm registry.example.com/app:v1.0.0 /bin/sh -c "echo 'works'"
    

Preguntas frecuentes

¿Cómo verifico por qué falló una descarga de imagen?

Ejecuta kubectl describe pod <pod-name> y busca la sección Events. Muestra el error exacto del registro, por ejemplo ‘no encontrada’ o ‘autenticación requerida’.

¿Cuál es la diferencia entre ImagePullBackOff y CrashLoopBackOff?

ImagePullBackOff significa que la descarga de imagen falló antes de que el contenedor siquiera iniciara. CrashLoopBackOff significa que el contenedor se ejecutó pero falló. Comprueba kubectl describe pod para ver cuál tienes.

¿Cómo uso un registro de Docker privado en Kubernetes?

Crea un Secret docker-registry, luego referenciarla en imagePullSecrets en tu spec de pod o service account. Muestra el comando kubectl create secret con tus credenciales del registro.

¿Por qué a veces fallan las descargas desde Docker Hub?

Docker Hub tiene límites de velocidad: 100 descargas por 6 horas para usuarios no autenticados, 200 por 6 horas para cuentas gratuitas. Después de alcanzar el límite, las descargas fallan con ‘Too Many Requests’. Auténtica con una cuenta de Docker Hub o usa un registro privado.

Cómo se verificó esta guía

Escrita a mano a partir de la documentación oficial, con cada comando comprobado de nuevo y una segunda revisión antes de publicar. Última revisión: .

Fuentes oficiales consultadas para este artículo

Share.

Los comentarios están cerrados.