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:
- 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.
- Private registry without imagePullSecrets — Kubernetes cannot authenticate to the registry and is rejected with “unauthorized” or “authentication required”.
- Docker Hub rate limit hit — Unauthenticated pulls from Docker Hub are limited. After 100 pulls per 6 hours, new pulls fail.
- 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.
- Registry is down, unreachable, or blocked — Network policy, firewall, DNS, or the registry server itself is offline or misconfigured.
- 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
-
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 -
Use specific tags, not
latest:image: registry.example.com/app:v1.2.3 # Good image: registry.example.com/app:latest # Risky -
Authenticate to private registries:
kubectl create secret docker-registry myregistrycred ... # And use it in all Deployments -
Pre-pull images on nodes using DaemonSets for frequently-used images, reducing pull time and failures.
-
Use a private registry mirror or cache (e.g., Harbor, Artifactory) to reduce dependency on Docker Hub rate limits.
-
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

