Skip to main content
DevOpsintermediate

ImagePullBackOff in Kubernetes: How to Diagnose and Fix

Fix ImagePullBackOff and ErrImagePull in Kubernetes. Read the kubelet Failed event, then tell a bad tag apart from a missing pull secret or rate limit.

9 min readUpdated August 2026

A pod stuck in ImagePullBackOff looks like this:

$ kubectl get pods
NAME                        READY   STATUS             RESTARTS   AGE
web-7d9c4b8f5c-x2klm        0/1     ImagePullBackOff   0          2m

ImagePullBackOff is not the error — it is the kubelet's response to an error. It tried to pull your image, the pull failed, and it is now waiting before retrying, with the delay growing after each attempt up to a five-minute cap. The status you often see first, for a few seconds, is ErrImagePull; once backoff begins it becomes ImagePullBackOff.

The actual reason is in the pod's events, and reading it takes one command:

kubectl describe pod web-7d9c4b8f5c-x2klm

Scroll to the bottom:

Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  2m                 default-scheduler  Successfully assigned default/web-7d9c4b8f5c-x2klm to node-1
  Normal   Pulling    2m (x3 over 2m)    kubelet            Pulling image "myrepo/web:v1.2.3"
  Warning  Failed     2m (x3 over 2m)    kubelet            Failed to pull image "myrepo/web:v1.2.3": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/myrepo/web:v1.2.3": failed to resolve reference: docker.io/myrepo/web:v1.2.3: not found
  Warning  Failed     2m (x3 over 2m)    kubelet            Error: ErrImagePull
  Normal   BackOff    1m (x6 over 2m)    kubelet            Back-off pulling image "myrepo/web:v1.2.3"
  Warning  Failed     1m (x6 over 2m)    kubelet            Error: ImagePullBackOff

The Failed event's message is the one that matters. Everything below is chosen by what it says.

Match the Message to the Cause

Message containsCauseFix
not found, manifest unknownWrong image name, tag, or registry pathFix 1
unauthorized, authentication requiredMissing or wrong pull secretFix 2
no such host, dial tcp, i/o timeoutNode cannot reach the registryFix 3
toomanyrequests, pull rate limitDocker Hub rate limitFix 4
InvalidImageName as the statusMalformed reference, not a pull failureFix 5

Fix 1: Wrong Image Name or Tag

By far the most common cause, and almost always a typo or a tag that was never pushed.

Check exactly what the spec asks for:

kubectl get pod web-7d9c4b8f5c-x2klm -o jsonpath='{.spec.containers[*].image}'

Then verify that reference exists from a machine with access:

docker manifest inspect myrepo/web:v1.2.3

If that fails the same way, the image genuinely is not there. Common variations:

  • The tag was never pushed — CI built v1.2.3 but the push step failed.
  • The registry host is missing, so myregistry.io/myrepo/web was written as myrepo/web and resolved against Docker Hub.
  • Case matters. MyRepo/Web is not myrepo/web.

Correct it in the deployment:

kubectl set image deployment/web web=myrepo/web:v1.2.4

Fix 2: Private Registry Without Credentials

An unauthorized message means the registry answered and refused you. The node needs credentials.

Create the secret in the same namespace as the pod:

kubectl create secret docker-registry regcred \
  --docker-server=myregistry.io \
  --docker-username=<user> \
  --docker-password=<token> \
  --namespace=default

Reference it in the pod spec:

spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: web
      image: myregistry.io/myrepo/web:v1.2.3

Two things trip people up here repeatedly:

Namespace scoping. Secrets are namespaced. A regcred in default does nothing for a pod in staging — you need one per namespace.

--docker-server must match the registry host in the image reference exactly. For Docker Hub the correct value is https://index.docker.io/v1/, not docker.io, and a mismatch means the credential is simply never applied.

To attach the secret to every pod in a namespace without editing each spec, patch the service account:

kubectl patch serviceaccount default -n default \
  -p '{"imagePullSecrets": [{"name": "regcred"}]}'

Use a read-only, pull-scoped token here rather than an account password. The secret is stored base64-encoded, not encrypted, so anyone who can read secrets in that namespace can read the credential.

Advertisement

Fix 3: The Node Cannot Reach the Registry

no such host or i/o timeout means this is a network problem, not an authentication one. The registry name never resolved, or the connection never completed.

Test from a pod on the affected node:

kubectl run nettest --rm -it --image=busybox --restart=Never -- \
  sh -c "nslookup myregistry.io && wget -S --spider https://myregistry.io/v2/"

Usual causes: a private registry only reachable inside a VPC that the node pool is not in; a NetworkPolicy blocking egress; a proxy the container runtime is not configured to use; or an internal registry using a certificate the node does not trust. Note that proxy settings for pulls belong to the container runtime on the node (containerd or CRI-O), not to your pod's environment variables — setting HTTPS_PROXY in the pod spec does nothing for image pulls.

Fix 4: Docker Hub Rate Limit

toomanyrequests: You have reached your pull rate limit.

Anonymous pulls from Docker Hub are limited per source IP. Every node behind one NAT gateway shares that budget, so a cluster hits the ceiling far faster than a laptop, and it typically appears as a sudden failure across many pods at once.

Two durable fixes: authenticate, using a pull secret as in Fix 2 even for public images, which raises the limit and attributes pulls to an account; or mirror the images you depend on into your own registry and reference them there, which also removes an external dependency from your deploys.

Fix 5: InvalidImageName

If the status is InvalidImageName rather than ImagePullBackOff, no pull was ever attempted — the reference itself is malformed and the kubelet rejected it. Look for an unsubstituted template variable (image: ${IMAGE} that Helm never filled in), a stray space, or a double colon. Print the resolved manifest to see what was actually applied:

kubectl get deployment web -o yaml | grep -A2 'image:'

Verify the Fix

After correcting the image or the secret, watch the pod recover:

kubectl get pods -w

You are looking for ImagePullBackOffContainerCreatingRunning.

Changing the image in the deployment triggers a new rollout automatically. Creating or fixing a secret does not — the existing pod keeps backing off with its old failure. Force a retry by deleting the pod, which the controller recreates:

kubectl delete pod web-7d9c4b8f5c-x2klm

Or restart the whole deployment:

kubectl rollout restart deployment/web
kubectl rollout status deployment/web

Confirm the image the running pod actually landed on:

kubectl get pod -l app=web -o jsonpath='{.items[0].status.containerStatuses[0].image}'

Prevention

  • Pin tags or digests, never latest. A digest (web@sha256:...) is immutable and makes the pull reproducible; latest also forces imagePullPolicy: Always, so every registry hiccup becomes a failed deploy.
  • Verify the push before you deploy. A CI pipeline that deploys after a push step whose failure it ignores produces this error on a schedule.
  • Create pull secrets in every namespace you deploy to, or attach them to the service account once.
  • Mirror third-party images into your own registry so an upstream rate limit or deletion cannot break your deploys.
  • Alert on pods not Ready. ImagePullBackOff retries forever and never fails the deployment on its own, so without an alert a broken rollout can sit unnoticed indefinitely.

Frequently Asked Questions

Find answers to common questions

The kubelet tried to pull your container image, failed, and is now waiting before trying again with an increasing delay. ImagePullBackOff is the backoff state, not the original failure — the reason the pull failed is in the pod's events, which you read with kubectl describe pod.

ErrImagePull is the first failure. ImagePullBackOff is what the status becomes after the kubelet starts backing off between retries. You will usually see ErrImagePull briefly and then ImagePullBackOff, and both point at the same underlying problem.

Run 'kubectl describe pod ' and read the Events section at the bottom. The Failed event contains the real message from the container runtime — image not found, unauthorized, no such host, or a rate limit. Everything you need to choose a fix is in that one line.

Create a pull secret with 'kubectl create secret docker-registry regcred --docker-server= --docker-username= --docker-password=', then reference it under imagePullSecrets in the pod spec. The secret must live in the same namespace as the pod — this is the most common reason a correct secret still fails.

Your laptop has credentials in ~/.docker/config.json that the cluster does not, and your nodes may resolve or reach the registry differently. A local pull proves the image exists; it proves nothing about whether the node can authenticate to or reach the registry.

Not necessarily. A missing image is one cause, but an unauthorized pull, an unreachable registry, an expired credential, and a Docker Hub rate limit all produce the same status. The Failed event distinguishes them — 'not found' versus 'unauthorized' versus 'no such host' versus 'toomanyrequests'.

The event says 'toomanyrequests: You have reached your pull rate limit'. Anonymous pulls are limited per IP, and every node behind one NAT gateway shares that budget. Add a pull secret with an authenticated Docker Hub account, or mirror the images you depend on into your own registry.

With imagePullPolicy Always — the default for the latest tag — the kubelet re-pulls on every pod start, so any registry problem surfaces immediately instead of being masked by a cached image. Pinning a specific tag or digest makes pulls cacheable and deployments reproducible.

Indefinitely, with exponential backoff capped at five minutes between attempts. A pod will sit in ImagePullBackOff forever rather than failing outright, so nothing times out on your behalf — you have to fix the cause and, if you changed a secret rather than the pod spec, delete the pod so it is recreated.