ErrImagePull means a pull attempt failed. ImagePullBackOff means Kubernetes has been retrying with increasing delay. Same problem, different stage.
Unlike CrashLoopBackOff, this one usually tells you exactly what is wrong — the registry's own error is quoted verbatim in the Pod events.
Read the event first
kubectl describe pod payments-api-7d4f-x8k2
Skip to the bottom. The Failed event carries the registry's response, and that response maps directly to a cause:
| Message contains | Cause |
|---|---|
not found / manifest unknown | Wrong image name or tag — cause 1 |
unauthorized / authentication required | Missing or wrong imagePullSecrets — cause 2 |
toomanyrequests | Docker Hub rate limit — cause 3 |
no such host / i/o timeout | Network or DNS from the node — cause 4 |
x509 / certificate signed by unknown authority | TLS trust — cause 5 |
# Just the events, quickly
kubectl get events --field-selector involvedObject.name=payments-api-7d4f-x8k2 \
--sort-by=.lastTimestamp
Cause 1 — Wrong image name or tag
By far the most common, and usually a typo or a tag that was never pushed.
# What does the Pod actually reference?
kubectl get pod <pod> -o jsonpath='{.spec.containers[*].image}{"\n"}'
Then confirm the tag exists in the registry:
# ECR
aws ecr describe-images --repository-name payments-api \
--query 'imageDetails[].imageTags' --output text
# Any registry, if you can reach it locally
docker manifest inspect ghcr.io/acme/payments-api:1.4.2
Three specific traps:
A CI pipeline that tagged differently from what the manifest expects. Building :abc1234 and deploying :latest produces exactly this.
imagePullPolicy: Always on a locally-built image. On kind or minikube, an image you built locally exists on the node but Kubernetes tries the registry anyway. Load it in — kind load docker-image myapp:dev or minikube image load myapp:dev — and set imagePullPolicy: IfNotPresent. Note that a tag of latest, or no tag at all, defaults the policy to Always.
Architecture mismatch. An arm64 image built on an Apple Silicon Mac pulled onto amd64 nodes fails with no matching manifest for linux/amd64. Build multi-arch with docker buildx build --platform linux/amd64,linux/arm64.
Cause 2 — Missing or wrong imagePullSecrets
Failed to pull image: unauthorized: authentication required
Private registries need credentials, and Kubernetes will not guess them.
kubectl create secret docker-registry regcred \
--docker-server=ghcr.io \
--docker-username=acme-ci \
--docker-password="$GITHUB_TOKEN" \
--namespace payments
spec:
imagePullSecrets:
- name: regcred
containers:
- name: api
image: ghcr.io/acme/payments-api:1.4.2
The three things that catch people:
Secrets are namespaced. A regcred in default does nothing for a Pod in payments. This is the single most frequent version of this problem.
The Pod spec must reference it. Creating the Secret is not enough — imagePullSecrets must name it. To avoid repeating that everywhere, attach it to the namespace's ServiceAccount instead:
kubectl patch serviceaccount default -n payments \
-p '{"imagePullSecrets":[{"name":"regcred"}]}'
--docker-server must match the registry host in the image. ghcr.io for GitHub, index.docker.io for Docker Hub, the full <account>.dkr.ecr.<region>.amazonaws.com for ECR. A mismatch produces a Secret that exists and is never used.
Verify what the Secret actually contains:
kubectl get secret regcred -n payments \
-o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .
On EKS, prefer the node role
For ECR, you usually do not need a Secret at all — give the node group's IAM role AmazonEC2ContainerRegistryReadOnly and the kubelet authenticates automatically. If ECR pulls fail on EKS, check that policy before creating credentials.
Cause 3 — Docker Hub rate limit
Failed to pull image: toomanyrequests: You have reached your pull rate limit
Docker Hub limits anonymous pulls per IP address. A cluster where every node shares a NAT gateway address hits it quickly, and it appears suddenly on a cluster that worked yesterday.
Three fixes, in order of durability:
Authenticate, even with a free account, which raises the limit substantially — create a docker-registry Secret for index.docker.io and attach it to the ServiceAccount.
Mirror the images you depend on into your own registry. ECR pull-through cache does this automatically and removes the dependency entirely.
Use a different registry. Many official images are also published to public.ecr.aws or ghcr.io, neither of which rate-limits the same way.
Cause 4 — The node cannot reach the registry
Failed to pull image: dial tcp: lookup ghcr.io: no such host
The kubelet pulls images, not your laptop — so the node's network path is what matters.
# Which node, so you can check the right one?
kubectl get pod <pod> -o wide
For private-subnet nodes, this usually means no NAT gateway, or missing VPC endpoints. On AWS, ECR needs endpoints for ecr.api and ecr.dkr plus a gateway endpoint for S3, because image layers are stored in S3 — missing the S3 endpoint is a classic half-configured setup where authentication succeeds and the layer download hangs.
CoreDNS failing will also present this way, so check that the cluster's DNS pods are healthy.
Cause 5 — TLS certificate problems
x509: certificate signed by unknown authority
Almost always a private registry with a self-signed or internal CA certificate the nodes do not trust. The fix is adding the CA to each node's trust store, or configuring the container runtime's registry settings — which is node-level configuration, not something a Pod spec can solve.
After you fix it
The backoff timer keeps running, so a fixed Pod may sit in ImagePullBackOff for up to five minutes before retrying. Delete it to force an immediate attempt:
kubectl delete pod <pod>
# or, for a Deployment
kubectl rollout restart deployment/<name>
A checklist
kubectl describe pod <pod>and read theFailedevent verbatim.not found→ check the image reference and that the tag was actually pushed.unauthorized→ is the Secret in this namespace, referenced, and for the right server?toomanyrequests→ authenticate to Docker Hub or mirror the image.no such host→ check the node's network path and DNS, not your laptop's.x509→ add the registry CA to the nodes.- Delete the Pod to skip the backoff timer once fixed.
Frequently Asked Questions
What is the difference between ErrImagePull and ImagePullBackOff?
They are the same underlying problem at different stages. ErrImagePull appears when a pull attempt fails. After a few failures Kubernetes starts waiting between attempts, and the status becomes ImagePullBackOff — the "BackOff" referring to that increasing delay, which caps at five minutes. Neither tells you the cause on its own; the Failed event in kubectl describe pod quotes the registry's actual error, which does.
How do I fix ImagePullBackOff for a private registry?
Create a Secret with kubectl create secret docker-registry in the same namespace as the Pod, and reference it under imagePullSecrets in the Pod spec. Three details cause most failures: Secrets are namespaced so one in default does nothing elsewhere; creating it is not enough without the Pod referencing it; and --docker-server must match the registry host in the image reference exactly. Attaching the Secret to the namespace's ServiceAccount avoids repeating the reference in every manifest.
Why does ImagePullBackOff happen with a local image on minikube or kind?
Because the cluster cannot see your machine's Docker images. On kind, run kind load docker-image myapp:dev; on minikube, either minikube image load myapp:dev or build inside its daemon with eval $(minikube docker-env). Also set imagePullPolicy: IfNotPresent, because Always — which is the default when the tag is latest or omitted — makes the kubelet attempt a registry pull that fails even though the image is present locally.
What does "toomanyrequests" mean when pulling an image?
You have hit Docker Hub's pull rate limit, which applies per IP address for anonymous pulls. Because all your nodes typically share one NAT gateway address, a cluster consumes the allowance far faster than a single developer would, and the failure appears abruptly on a cluster that worked the previous day. Authenticate with a Docker Hub account to raise the limit, or mirror the images you depend on into your own registry — ECR pull-through cache removes the dependency entirely.
Why do my ECR pulls fail on EKS?
Usually the node group's IAM role is missing AmazonEC2ContainerRegistryReadOnly, which is how the kubelet authenticates to ECR without any Secret. The other common cause is networking: nodes in private subnets need VPC endpoints for ecr.api and ecr.dkr, plus a gateway endpoint for S3, because ECR stores image layers in S3. Missing the S3 endpoint produces a half-working setup where authentication succeeds and the layer download times out.
My image exists but Kubernetes says "no matching manifest". Why?
The image was built for a different CPU architecture than your nodes — most often an arm64 image built on an Apple Silicon Mac being pulled onto amd64 nodes. Build a multi-architecture image with docker buildx build --platform linux/amd64,linux/arm64 --push, or build explicitly for the target with --platform linux/amd64. The error names the platform it could not find, which makes it easy to confirm once you know to look for it.