On this page
ImagePullBackOff: What It Means and How to Fix It
ImagePullBackOff means Kubernetes cannot download your container image. Learn the causes — bad tag, auth, rate limit, network — and how to diagnose each.
Quick answer
ErrImagePullis the first failure;ImagePullBackOffmeans it has failed repeatedly and Kubernetes is waiting before retrying.- The container never started, so container logs are empty — the reason is in
kubectl describe podevents and the node’s kubelet log.- The usual causes: a tag that does not exist, missing registry credentials, anonymous pull rate limits, or no network egress from the node.
- If it works with
docker pulllocally but not in the cluster, it is almost always authentication.
What is ImagePullBackOff?
ImagePullBackOff is a pod status meaning the kubelet could not download the container image, failed repeatedly, and is waiting a progressively longer interval before trying again. The state appears alongside ErrImagePull, which is the single-attempt error.
The key point for diagnosis: this is a pulling failure, not a running failure. The container has never been created, so kubectl logs returns nothing useful, and the cause lives entirely in the registry interaction. That is why the diagnostic path is different from a crash-looping container, where the logs are the answer.
Where this bites vibecoders
The specific way this bites vibecoders is a manifest that only works on the machine that wrote it. An assistant produces
image: myapp:latest, you build and run it locally where the image already exists, and the deployment ships without adocker pushanyone asked for. The pod is scheduled fine, then sits inImagePullBackOffforever — while the identical tag pulled successfully from your laptop minutes earlier, which is exactly why it reads as a Kubernetes bug rather than a missing push.
What causes ImagePullBackOff?
| Cause | Error text | How it usually happens |
|---|---|---|
| Tag does not exist | manifest unknown / not found | You built and pushed with a different tag, or used :latest and never pushed |
| Registry name wrong | failed to resolve reference | Typo, or a short name Docker Hub resolves but the node does not |
| No credentials | unauthorized / authentication required | Private image, no imagePullSecrets configured |
| Rate limited | toomanyrequests | Anonymous pulls from Docker Hub exhausted |
| Image not pushed | manifest unknown | Built locally, never docker pushed |
| No network from the node | i/o timeout / dial tcp | Egress blocked, DNS broken, proxy required |
| Arch mismatch / platform | no matching manifest | Image built for a different CPU architecture |
| Registry unreachable or down | dial tcp: lookup ... | DNS, firewall, or an outage at the registry |
The first two and the third account for most cases. Start there.
How do I diagnose it?
Step 1 — Read the pod events. This narrows it to a category immediately:
kubectl describe pod my-app-7d9f8b6c4-x2k9pThe Events: section reports one of these shapes:
Normal Pulling 2m kubelet Pulling image "myapp:v1.2.3"
Warning Failed 2m kubelet Failed to pull image "myapp:v1.2.3":
rpc error: code = NotFound desc = failed to resolve
reference: myapp:v1.2.3 not found
Normal BackOff 2m kubelet Back-off pulling image "myapp:v1.2.3"failed to resolve reference ... not found points at a tag or registry name. failed to pull and unpack image points at the node. For anything more specific, continue to step 2.
Step 2 — Read the kubelet log on the node. The registry’s actual error is here, and it is the most precise evidence available:
# On the node
journalctl -u kubelet --since "10 min ago" | grep -i "imagepull\|failed to pull"Look for manifest unknown, unauthorized, toomanyrequests, or a dial/DNS error. Each maps directly to a fix below.
Step 3 — Reproduce the pull from the node. The definitive test, because it uses the same credentials and network the kubelet has:
# Does the image exist under that exact name and tag?
docker pull myapp:v1.2.3
# Does the registry answer at all?
curl -sI https://registry-1.docker.io/v2/ | head -1If this fails on the node but succeeds on your laptop, the difference is credentials or network — not the image.
How do I fix each cause?
The tag does not exist. Confirm what you actually pushed, then fix the manifest to match:
# What tags exist locally?
docker images
# What did you push?
docker push myapp:v1.2.3Then set an explicit, immutable tag in the deployment rather than :latest, so the manifest and the registry cannot drift apart. imagePullPolicy also matters: with a :latest tag Kubernetes defaults to Always, while a specific tag defaults to IfNotPresent and reuses a cached copy.
Missing credentials for a private registry. Create a registry secret and reference it from the pod:
kubectl create secret docker-registry regcred \
--docker-server=ghcr.io \
--docker-username=YOUR_GITHUB_USER \
--docker-password=YOUR_TOKENspec:
containers:
- name: app
image: ghcr.io/your-org/myapp:v1.2.3
imagePullPolicy: IfNotPresent
imagePullSecrets:
- name: regcredUse a short-lived token, not a personal password, and never commit one into a manifest — that is the scenario covered in How to Scan Your Codebase for Hardcoded Secrets and How to Automate API Key Rotation.
Hit an anonymous pull rate limit. Docker Hub limits unauthenticated pulls. The fix is to authenticate — the same secret mechanism above — or to use a registry that does not rate-limit anonymous pulls for your workload. The error text is toomanyrequests.
No network from the node. Check DNS and egress from the node itself:
# Can the node resolve and reach the registry?
curl -sI https://registry-1.docker.io/v2/ | head -1
getent hosts registry-1.docker.ioA private cloud VPS with an outbound firewall or a restrictive egress policy needs a registry mirror or a proxy configured on the node, not in your application.
Architecture mismatch. If the error is no matching manifest for linux/arm64, the image was built for x86 and the node is ARM. Build for the right platform:
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:v1.2.3 --push .Verify the fix. Watch the status change:
kubectl get pod -wErrImagePull / ImagePullBackOff should become ContainerCreating and then Running. If it stays in backoff, delete and recreate the pod so the kubelet starts the pull cycle fresh:
kubectl delete pod my-app-7d9f8b6c4-x2k9pWhy does this only happen in Kubernetes?
Because the kubelet is a different client from your shell, with different credentials and a different network path. The usual cases:
- Your machine is logged in, the node is not.
docker pulluses your local credentials file; the kubelet has none unless you give it animagePullSecret. - You built the image locally and never pushed it. The kubelet pulls from a registry, not from your laptop, and the image does not exist there.
- Your laptop is behind a corporate proxy, the node is not, so the node cannot reach the registry.
- Short names resolve differently.
nginxandmyappare resolved against Docker Hub by default; an unresolvable short name for your private image fails on the node.
Where AI coding assistants get this wrong
- Using
:latest, so the tag cannot be verified andimagePullPolicy: Alwaysforces a network round trip on every start. - Referencing an image that was built locally but never pushed, then suggesting a build step in the cluster that does not exist.
- Omitting
imagePullSecretsfor a private registry, while the local machine works fine because it has cached credentials. - Hardcoding a registry hostname in a manifest that is only correct on the developer’s machine.
- Not distinguishing
ErrImagePullfromPendingand chasing scheduling problems when the real issue is the image. - No platform specified when building, so an ARM node cannot run an x86 image — the
arm vs x86distinction is covered in ARM vs x86. - Treating a
Back-off pullingline as the cause, when it is only the kubelet’s retry behaviour; the real reason is in theFailedevent immediately above it.
Checklist
- Run
kubectl describe podand read theFailedevent, not theBackOffone. - Check the kubelet log for the registry’s actual error:
journalctl -u kubelet | grep -i "failed to pull". - Confirm the image and tag exist in the registry, not just on your machine.
- Push anything you built locally before deploying it.
- Use explicit, immutable tags and set
imagePullPolicydeliberately. - Create an
imagePullSecretsentry for private registries, using a short-lived token. - Authenticate to Docker Hub to avoid anonymous pull rate limits.
- Verify DNS and egress from the node, not from your laptop.
- Build for the node’s architecture if it is ARM.
- Quote the full registry path (
ghcr.io/org/app) rather than a short name. - Watch
kubectl get pod -wto confirm the pod reachesRunning.
FAQ
What is ImagePullBackOff?
ImagePullBackOff means the kubelet tried to download the container image, failed, and is backing off before retrying. The first failure shows as ErrImagePull; after repeated failures Kubernetes reports ImagePullBackOff. It is a pulling problem, not a running problem — the container has never started.
How do I see the actual reason the image pull failed?
Use kubectl describe pod and read the events, then check the node’s kubelet logs. The pod events give a summary such as failed to resolve reference, while the kubelet log has the specific error from the registry — manifest unknown, unauthorized, or toomanyrequests.
Why do I get ImagePullBackOff only in Kubernetes but not locally?
Usually authentication. Docker on your machine is logged in, and the node is not, so the private pull works locally and fails in the cluster. The other common cause is rate limiting, which applies to anonymous pulls from public registries regardless of what your laptop can do.
What is the difference between ErrImagePull and ImagePullBackOff?
ErrImagePull is the immediate error from a single failed attempt. Once several attempts have failed, Kubernetes reports ImagePullBackOff to indicate it is retrying with increasing delay. They describe the same underlying problem at two moments in the retry cycle.
Should I use the latest tag in production?
No. A mutable tag means the image can change without your manifest changing, which removes the link between what you deployed and what is running, and it forces a registry round trip on every pod start. Use a commit hash or a release tag, and record which one you deployed.
Does deleting the pod fix ImagePullBackOff?
Deleting the pod restarts the pull cycle, which helps if the failure was transient or if you just pushed a fixed image to the same tag. It does not help if the tag does not exist or credentials are missing, because the retry will fail identically. Fix the cause first, then recreate the pod.
Related topics
- What Is a Container Registry (and How Do Rate Limits Work)?
- Why Is My Kubernetes Pod Stuck Pending?
- How to Debug a Crash-Looping Container
- How to Write a Secure Dockerfile
- Kubernetes YAML Explained: Deployments, Services, and What Your AI Writes