1. Introduction
If you've deployed a workload to Kubernetes and your pod is stuck in an ImagePullBackOff state, the kubelet couldn't pull the container image it needs. The pod won't start — and it won't tell you exactly why unless you know where to look.
This guide walks through the most common causes of ImagePullBackOff and gives you a clear, ordered diagnostic process to fix it fast. Whether you're dealing with a wrong image tag, a misconfigured registry secret, or a network restriction, you'll find the cause and the fix here.
2. What ImagePullBackOff Actually Means
When Kubernetes schedules a pod, the kubelet on the assigned node attempts to pull the container image from the registry. If that pull fails, Kubernetes transitions the pod through two states:
- ErrImagePull — the initial failed pull attempt
- ImagePullBackOff — Kubernetes backing off from retrying, with increasing delays
The "BackOff" part is important. Kubernetes won't keep hammering the registry — it uses an exponential backoff strategy, which means retries get further apart over time. The pod stays in this state until the underlying issue is resolved or the pod is deleted.
3. Common Causes
- Wrong image name or tag (typo, outdated tag, deleted tag)
- Image does not exist in the specified registry
- Private registry with no imagePullSecret configured
- Expired or incorrect registry credentials
- The node can't reach the registry (network policy, proxy, firewall)
- Registry rate limiting (DockerHub pulls without auth)
- Wrong registry URL for private/self-hosted registries
4. Step-by-Step Fix
Step 1: Get the exact error message
Start by describing the pod. This gives you the actual pull error, not just the state name:
kubectl describe pod <pod-name> -n <namespace>
# Look for the Events section at the bottom:
# Events:
# Warning Failed 2m kubelet Failed to pull image
# "myrepo/myapp:v1.2": rpc error: code = Unknown
# desc = failed to pull and unpack image
# "docker.io/myrepo/myapp:v1.2":
# unexpected status code 401 Unauthorized
The error code in the Events section is your primary diagnostic signal. Note it before moving to the next step.
Step 2: Verify the image reference
Check that the image name and tag in your manifest exactly match what exists in the registry.
# Check what image the pod is trying to pull kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.containers[*].image}'
Common problems here:
- Using latest when that tag no longer exists or has been removed
- A typo in the image name — registries are case-sensitive
- Referencing a tag from a build that hasn't been pushed yet
Step 3: Check if the image exists
If the tag looks right, confirm the image actually exists in the registry:
# Docker Hub / public registry — check via CLI docker manifest inspect myrepo/myapp:v1.2
# AWS ECR aws ecr describe-images --repository-name myapp --image-ids imageTag=v1.2
# GCR / Artifact Registry gcloud container images list-tags gcr.io/my-project/myapp
If the image doesn't exist, fix the push step in your CI/CD pipeline before re-deploying.
Step 4: Check imagePullSecrets for private registries
If you're using a private registry (ECR, GCR, Docker Hub private, self-hosted), the node needs credentials. Check whether the pod has a pull secret configured:
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.imagePullSecrets}'
# Expected output if secret is set:
# [{"name":"my-registry-secret"}]
# Check the secret exists in the correct namespace: kubectl get secret my-registry-secret -n <namespace>
If there's no pull secret, or the secret doesn't exist in the pod's namespace, that's your issue. Create one:
# For Docker Hub / generic registry kubectl create secret docker-registry my-registry-secret \ --docker-server=https://index.docker.io/v1/ \ --docker-username=<username> \ --docker-password=<password> \ --docker-email=<email> \ -n <namespace>
# Then reference it in your pod spec or service account:
# spec:
# imagePullSecrets:
# - name: my-registry-secret
Step 5: Validate the secret credentials
A secret can exist with stale or invalid credentials. Verify by decoding and testing manually:
# Decode the .dockerconfigjson field kubectl get secret my-registry-secret -n <namespace> \ -o jsonpath='{.data.\.dockerconfigjson}' | base64 --decode
# You should see a JSON object with auths, username, password
# Test the credentials by trying to pull from the same node or a local machine docker login <registry-url> -u <username> -p <password> docker pull <image>:<tag>
Step 6: Check network access to the registry
If credentials look fine, the node may not be able to reach the registry. This is common in:
- Private EKS clusters with no NAT gateway or VPC endpoint for ECR
- Clusters with restrictive NetworkPolicies or egress firewall rules
- Air-gapped environments with a private mirror not correctly configured
To test connectivity from the node:
# Find which node the pod is scheduled on kubectl get pod <pod-name> -n <namespace> -o wide
# SSH to the node (or use a debug pod on the same node) kubectl debug node/<node-name> -it --image=busybox
# Inside the debug pod, test DNS + HTTPS connectivity nslookup registry-1.docker.io wget -q --spider https://registry-1.docker.io/v2/
If DNS resolves but the HTTPS request hangs or fails, check your security groups, NACLs, and any egress network policies. For ECR on EKS in a private cluster, you need a VPC endpoint for ECR (both ecr.api and ecr.dkr). For a deeper dive on ECR-specific authentication errors, see ECR Login Failed: Fix AWS ECR Authentication.
Step 7: Check for rate limiting
Docker Hub limits unauthenticated pulls to 100/6h per IP. On a busy cluster with many nodes sharing a NAT IP, this can trigger ImagePullBackOff:
# The error message will contain:
# toomanyrequests: You have reached your pull rate limit
# Fix: authenticate pulls even for public images
# Create a Docker Hub pull secret (even free account significantly raises limits)
# Or use a registry mirror / pull-through cache
5. Verification Steps
Once you've applied a fix, verify the pod recovers:
# Watch pod status in real time kubectl get pod <pod-name> -n <namespace> -w
# Expected transition:
# NAME READY STATUS RESTARTS
# myapp-xyz 0/1 ImagePullBackOff 0
# myapp-xyz 0/1 Init:0/1 0
# myapp-xyz 0/1 PodInitializing 0
# myapp-xyz 1/1 Running 0
# If the pod doesn't recover automatically, force a rollout: kubectl rollout restart deployment/<deployment-name> -n <namespace>
6. Common Mistakes
- Creating the imagePullSecret in a different namespace to the pod — secrets are namespace-scoped
- Attaching the pull secret to the pod spec but not to the service account used by the pod
- Regenerating an ECR token manually then forgetting to update the Kubernetes secret
- Using a private image name format that assumes default registry (e.g., myapp:v1 instead of myregistry.com/myapp:v1)
- Checking the wrong pod or namespace — always confirm with kubectl get pod -A if something seems off
7. Prevention Tips
- Pin image tags to specific versions or digests — never rely on :latest in production
- Use an image admission controller (e.g., Kyverno or OPA) to enforce tag pinning
- For ECR, automate credential rotation using IRSA + the ECR credential helper rather than static secrets
- Set up a registry mirror or pull-through cache to reduce external dependency and rate-limit exposure
- Add image pull validation to your CI/CD pipeline — verify the image exists and is pullable before deploying
- Monitor ImagePullBackOff events with Prometheus + Alertmanager (kube_pod_container_status_waiting_reason metric)
8. Summary
ImagePullBackOff means Kubernetes can't pull the container image. The fix depends on the underlying cause:
| Cause | Fix |
|---|---|
| Wrong image tag | Correct the tag in your manifest; verify it exists in the registry |
| Image doesn't exist | Push the image first; fix your CI build/push step |
| No imagePullSecret | Create a docker-registry secret and reference it in the pod spec |
| Bad credentials | Decode the secret and verify login; recreate with valid credentials |
| Network / firewall | Check node-to-registry connectivity; add VPC endpoints for ECR |
| Rate limiting | Authenticate DockerHub pulls; use a pull-through cache |
When in doubt, start with kubectl describe pod — the Events section almost always points you directly at the cause.