ImagePullBackOff means Kubernetes tried to download your container image but failed. The pod waits, retries, and fails again in an exponential backoff loop. This guide shows you how to diagnose and fix the most common causes.

Symptom

The pod status shows ImagePullBackOff. Running kubectl describe pod reveals:

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"

Why it happens

Ranked by likelihood:

  1. Wrong image name or tag — The image does not exist in the registry. Typo in the name, tag does not exist, or the image was never pushed.
  2. Private registry without imagePullSecrets — Kubernetes cannot authenticate to the registry and is rejected with “unauthorized” or “authentication required”.
  3. Docker Hub rate limit hit — Unauthenticated pulls from Docker Hub are limited. After 100 pulls per 6 hours, new pulls fail.
  4. Wrong architecture or OS — The image is built for a different CPU architecture (e.g., arm64 vs amd64) or OS, and the runtime cannot execute it.
  5. Registry is down, unreachable, or blocked — Network policy, firewall, DNS, or the registry server itself is offline or misconfigured.
  6. Expired or invalid credentials — The registry credentials in the Secret have changed or the token has expired.

Fix

1. Wrong image name or tag

Check what you specified:

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

Verify the image exists in the registry:

# For Docker Hub
docker pull example:latest

# For a private registry
docker pull registry.example.com/example:latest

# If the pull fails locally, it will fail in Kubernetes too

If the image does not exist, push it first:

# Build the image
docker build -t example:latest .

# Tag it for your registry
docker tag example:latest registry.example.com/example:latest

# Push it
docker push registry.example.com/example:latest

Then update your Deployment to use the correct image:

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

Apply and retry:

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

2. Private registry without imagePullSecrets

If the registry is private, Kubernetes needs credentials. Create a docker-registry Secret:

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

Or from a docker login config:

# If you have already logged in locally, copy your credentials
kubectl create secret generic myregistrycred \
  --from-file=.dockerconfigjson=$HOME/.docker/config.json \
  --type=kubernetes.io/dockercfg

Verify the secret was created:

kubectl get secret myregistrycred -o yaml

Then reference it in your pod spec:

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

Alternatively, add the secret to your service account so all pods in that account use it:

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

Verify:

kubectl get serviceaccount default -o yaml

Then delete and recreate the pod to pull with the new secret:

kubectl delete pod <pod-name>

3. Docker Hub rate limit hit

If you are pulling unauthenticated from Docker Hub, you hit the 100-pulls-per-6-hours limit. Authenticate instead:

Create a Docker Hub Personal Access Token (or use your password) and create a secret:

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

Update your Deployment to reference it:

imagePullSecrets:
- name: dockerhubcred

Or add it to the default service account:

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

For higher limits, use a paid Docker Hub account or a private registry. Delete and recreate pods to retry with the new credentials:

kubectl delete pod <pod-name>

4. Wrong architecture or OS

The image was built for a different CPU or OS. Check what architecture the node has:

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

Check what architecture the image supports. Build it for the correct architecture using Docker BuildX:

# Build for multiple architectures
docker buildx build --platform linux/amd64,linux/arm64 \
  -t registry.example.com/example:latest \
  --push .

# Or build only for your target
docker buildx build --platform linux/arm64 \
  -t registry.example.com/example:arm64 .

You can also use a multi-architecture base image:

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

If the image is already built for the wrong architecture, rebuild it or use a different base image.

5. Registry is down, unreachable, or blocked

Test network connectivity to the registry:

# From the cluster
kubectl run debug --image=busybox -it --rm -- sh
# Inside the container:
wget https://registry.example.com
nc -zv registry.example.com 443

Check DNS resolution:

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

Check for network policies that might block egress:

kubectl get networkpolicy -A

Check cluster firewall rules. If the registry is within the cluster (e.g., a private Harbor instance), ensure:
– The registry pod is running: kubectl get pod -n registry
– The registry service is reachable: kubectl get svc -n registry

Wait for the registry to come back online, then delete the pod to retry:

kubectl delete pod <pod-name>

6. Expired or invalid credentials

If you updated credentials in your private registry, update the Kubernetes Secret:

# Delete the old secret
kubectl delete secret myregistrycred

# Create a new one with updated credentials
kubectl create secret docker-registry myregistrycred \
  --docker-server=registry.example.com \
  --docker-username=myuser \
  --docker-password=mynewpass \
  --docker-email=me@example.com

Force pods to re-authenticate by deleting them:

kubectl delete pod <pod-name>

For token-based authentication (e.g., Kubernetes ServiceAccount tokens used by in-cluster registries), regenerate the token and update the Secret.

Prevent it

  1. Push the image before deploying:

    docker build -t registry.example.com/app:v1.0.0 .
    docker push registry.example.com/app:v1.0.0
    # Then deploy with that exact tag
    

  2. Use specific tags, not latest:

    image: registry.example.com/app:v1.2.3    # Good
    image: registry.example.com/app:latest    # Risky
    

  3. Authenticate to private registries:

    kubectl create secret docker-registry myregistrycred ...
    # And use it in all Deployments
    

  4. Pre-pull images on nodes using DaemonSets for frequently-used images, reducing pull time and failures.

  5. Use a private registry mirror or cache (e.g., Harbor, Artifactory) to reduce dependency on Docker Hub rate limits.

  6. Test image pull before committing to Kubernetes:

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

FAQ

How do I check why an image pull failed?

Run kubectl describe pod <pod-name> and look at the Events section. It shows the exact registry error, e.g. ‘not found’ or ‘authentication required’.

What is the difference between ImagePullBackOff and CrashLoopBackOff?

ImagePullBackOff means the image download failed before the container even started. CrashLoopBackOff means the container ran but crashed. Check kubectl describe pod to see which one you have.

How do I use a private Docker registry in Kubernetes?

Create a docker-registry Secret, then reference it in imagePullSecrets in your pod spec or service account. Show the kubectl create secret command with your registry credentials.

Why does pulling from Docker Hub sometimes fail?

Docker Hub has rate limits: 100 pulls per 6 hours for unauthenticated users, 200 per 6 hours for free accounts. After hitting the limit, pulls fail with ‘Too Many Requests’. Authenticate with a Docker Hub account or use a private registry.

How this guide was verified

Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .

Official sources read for this article

Share.

Haythem Rehouma builds and teaches DevOps and AI tooling: Kubernetes, Docker, Terraform, Claude Code, ChatGPT and n8n. Every KubPoint guide is written from the official docs and re-tested before it goes live.

Comments are closed.