Quick Answer (TL;DR)
ImagePullBackOff means the kubelet cannot pull your container image and is now backing off before retrying. The fix is almost always one of three things: a typo in the image name or tag, a missing imagePullSecret for a private registry, or an architecture mismatch (ARM vs AMD64). Run kubectl describe pod <pod-name> and read the Events section. The exact reason is printed there in plain English.
Why this happens
Kubernetes asks the kubelet on each node to pull the image referenced in your pod spec. The kubelet contacts the container registry, authenticates if required, and downloads the image layers. ImagePullBackOff fires when that sequence fails repeatedly. The "BackOff" part means Kubernetes is now waiting an exponentially growing delay before trying again. A related error called ErrImagePull fires on the first failure, before the backoff kicks in. Both share the same root causes, so the same fixes apply to either.
Fix #1: Verify the image string and read pod events
This catches the majority of cases. Start by reading what Kubernetes actually saw:
kubectl describe pod <pod-name> -n <namespace>
Scroll to the Events section at the bottom. A typical failure event looks like this:
Warning Failed 10s (x4 over 35s) kubelet Failed to pull image
"myorg/myapp:v1.2": rpc error: code = Unknown desc = pull access
denied for myorg/myapp, repository does not exist or may require
authorization
That line tells you which fix to apply. Now compare the image string in your pod spec against the registry:
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].image}'
Read the result character by character. nginx:latset instead of nginx:latest, or v1.0.0-beta1 versus v1.0.0-beta-1, are extremely common. If the event mentions "repository does not exist," check whether the image actually exists in the registry:
docker manifest inspect ghcr.io/myorg/myapp:v1.2
If that returns "no such manifest," your CI never pushed the image. Re-run the build job and retry.
Fix #2: Create the imagePullSecret in the correct namespace
Private registries like GitHub Container Registry, Docker Hub private, AWS ECR, and Azure Container Registry need authentication. You create a Kubernetes secret and reference it from the pod spec:
spec:
imagePullSecrets:
- name: my-registry-secret
containers:
- name: app
image: ghcr.io/myorg/myapp:v1
Two things break here. First, the secret was never created. Second, the secret was created in the wrong namespace. Pull secrets are namespace-scoped, so a secret in default is invisible to a pod in production.
Check both:
kubectl get secret my-registry-secret -n production
If the result is "not found," create it in the right namespace:
kubectl create secret docker-registry my-registry-secret \
--docker-server=ghcr.io \
--docker-username=<your-username> \
--docker-password=<your-token> \
--namespace=production
For ECR specifically, the token expires every 12 hours. Static secrets created last week are already useless. Use the ECR credential helper or IAM-based authentication so the cluster gets fresh tokens automatically.
Fix #3: Use a multi-arch image for mixed-architecture nodes
This is the edge case that bites teams moving to AWS Graviton, GCP Tau T2A, or running Apple Silicon dev machines alongside x86 production nodes. The image is built only for linux/amd64, the pod schedules on a linux/arm64 node, and the pull fails because no matching manifest exists.
Confirm the mismatch:
docker manifest inspect myorg/myapp:v1 | grep architecture
If you see only amd64 but your nodes are arm64, rebuild the image as multi-arch:
docker buildx create --use
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t myorg/myapp:v1 \
--push .
A few honorable mentions also worth checking:
- Docker Hub rate limit. Anonymous pulls cap at 100 per 6 hours per IP. Events mention "toomanyrequests." Authenticate even for public images.
-
Node disk pressure. Image pulls need local disk. Run
kubectl describe node <node>and look forDiskPressure: True. Old image layers accumulate forever without GC. -
Network or firewall blocking the registry. Common in air-gapped or VPC-restricted clusters. Test from a debug pod with
wget -O- https://<registry>/v2/.
How to prevent this
A handful of habits eliminate most ImagePullBackOff incidents before they happen:
-
Pin to immutable tags in production. Never use
latestoutside of dev. Immutable tags expose registry problems immediately instead of silently serving stale images. - Use multi-arch builds by default. Even if you only run AMD64 today, the cost of building both architectures is small and saves you a migration headache later.
-
Centralize pull credentials. Run a tool like
kubedor use a GitOps controller (Flux, Argo) that replicatesimagePullSecretto every namespace automatically. Manual copies drift. - Mirror critical public images to your own registry. Docker Hub outages and rate limits stop being your problem. ECR, GCR, and Artifactory all support pull-through caches.
-
Set up image garbage collection. Configure
imageGCHighThresholdPercentandimageGCLowThresholdPercenton the kubelet so disk pressure never blocks pulls. - Authenticate every registry pull, even public. Authenticated Docker Hub free accounts get 200 pulls per 6 hours instead of 100 anonymous, and the audit trail helps debugging.
FAQ
Q: What is the difference between ImagePullBackOff and ErrImagePull?
A: ErrImagePull fires on the first pull failure. ImagePullBackOff is the state Kubernetes enters after repeated failures, when it starts delaying retries exponentially. Same root causes, same fixes.
Q: Why does my pod work in one namespace but fail in another?
A: imagePullSecret is namespace-scoped. A secret created in default is invisible to pods in any other namespace. You must create the secret in every namespace that needs it, or use a GitOps tool to replicate it.
Q: Will imagePullPolicy: Always fix ImagePullBackOff?
A: No. imagePullPolicy controls when Kubernetes tries to pull, not whether the pull succeeds. ImagePullBackOff is a network, auth, or image-existence failure. Policy changes do not help.
Q: How do I retry a stuck ImagePullBackOff faster instead of waiting for the backoff?
A: Delete the pod with kubectl delete pod <name>. The deployment recreates it, and the new pod starts pulling immediately without inherited backoff delay.
Q: How do I debug ImagePullBackOff on a managed Kubernetes service like EKS, GKE, or AKS?
A: Same approach. kubectl describe pod and read events. Managed services hide the node OS but the kubelet still emits identical error messages. For ECR-specific issues on EKS, also check that the node's IAM role has the AmazonEC2ContainerRegistryReadOnly policy attached.
Q: My image exists and the secret is correct, but I still get ImagePullBackOff. What now?
A: Check three things in order: node architecture (amd64 vs arm64), node disk space (DiskPressure), and network reachability to the registry from inside the node. One of those is almost always the culprit.
Related guides
-
Kubernetes Docs: Pull an Image from a Private Registry: official guide on
imagePullSecretconfiguration -
Kubernetes Docs: Images: full reference on
imagePullPolicy, manifest handling, and registry behavior - Docker Docs: Multi-platform images with buildx: building images that work on both AMD64 and ARM64 nodes
- AWS Docs: Amazon ECR pull through cache: mirror Docker Hub through ECR to avoid rate limits
-
GitHub: kubernetes-sigs/cri-tools:
crictlfor debugging pulls at the CRI layer whenkubectlis not enough
Did this help you unblock a stuck pod? What is the strangest ImagePullBackOff cause you have hit in production? Drop it in the comments. Weird debugging stories make the best Monday morning reading.
Top comments (0)