Fix Kubernetes ImagePullBackOff and ErrImagePull¶
Problem¶
A pod is stuck in ErrImagePull or ImagePullBackOff: the node can't download the container image, so the container never starts.
ErrImagePull is the first failed attempt. ImagePullBackOff means the kubelet is waiting before retrying, with delays that grow up to five minutes.
Symptoms¶
RESTARTSstays at0— the container has never run, so there are no logs.- Events show
Failed to pull image, followed byBack-off pulling image. - A rollout stalls with new pods stuck while old pods keep serving.
- It may affect only some nodes (for example, new nodes without registry access, or a different CPU architecture).
Likely Causes¶
The error message in the events almost always names the cause. Match it:
| Message in events (abridged) | Cause |
|---|---|
manifest unknown, not found |
Tag or digest doesn't exist in that repository |
pull access denied, repository does not exist or may require authorization |
Wrong repository name, or a private image with no credentials |
unauthorized: authentication required, 401 Unauthorized |
Credentials missing, wrong, or expired |
403 Forbidden, denied |
Credentials valid but not allowed to pull that repository |
toomanyrequests, You have reached your pull rate limit |
Registry rate limit (common with anonymous Docker Hub pulls) |
no match for platform in manifest |
Image not built for the node's CPU architecture |
dial tcp: lookup … no such host |
DNS resolution failing on the node |
i/o timeout, context deadline exceeded |
Network path to the registry blocked (firewall, NAT, proxy, missing VPC endpoint) |
x509: certificate signed by unknown authority |
Private registry with a CA the node doesn't trust |
Diagnostic Commands¶
# 1. The exact error
kubectl describe pod orders-api-7c9d8b6f5-k2x4p | sed -n '/Events:/,$p'
# 2. The image reference Kubernetes is trying to pull
kubectl get pod orders-api-7c9d8b6f5-k2x4p \
-o jsonpath='{range .spec.containers[*]}{.name}{"\t"}{.image}{"\n"}{end}'
# 3. Which pull secrets the pod actually has (directly or via its service account)
kubectl get pod orders-api-7c9d8b6f5-k2x4p -o jsonpath='{.spec.imagePullSecrets}{"\n"}'
kubectl get serviceaccount "$(kubectl get pod orders-api-7c9d8b6f5-k2x4p -o jsonpath='{.spec.serviceAccountName}')" -o yaml
# 4. Does the tag exist, and for which platforms? (from your workstation)
docker buildx imagetools inspect registry.example.com/orders-api:2.14.0
# 5. Which node, and its architecture
kubectl get pod orders-api-7c9d8b6f5-k2x4p -o wide
kubectl get node <node-name> -o jsonpath='{.status.nodeInfo.architecture}{"\n"}'
On a node you can access (or through kubectl debug node/<name> -it --image=busybox), pull the image with the container runtime directly to separate Kubernetes configuration from network and registry problems:
Step-by-Step Debugging¶
- Copy the exact error from events. Don't guess from the status — the message distinguishes a typo from an auth problem from a network problem.
- Verify the reference. Check registry host, repository path, tag, and digest character by character.
orders-api:2.14andorders-api:2.14.0are different tags. - Confirm the tag exists with
docker buildx imagetools inspect, and note the platforms it was built for. - Check credentials if the error mentions authorization: is a pull secret attached, does it target the right registry host, and is it still valid?
- Check the node's path to the registry if the error is a timeout or DNS failure — this is a network problem, not a Kubernetes manifest problem.
- Check whether only some nodes fail. If the pod works on one node pool but not another, compare architecture, network placement, and node IAM roles.
Solutions¶
The tag doesn't exist¶
Make CI deploy the exact tag or digest it just pushed, so a manifest can't reference an image that was never built.
Private registry credentials¶
kubectl create secret docker-registry regcred \
--namespace orders \
--docker-server=registry.example.com \
--docker-username=deploy-bot \
--docker-password="$REGISTRY_TOKEN"
# Attach to the service account the pods use, so every workload inherits it
kubectl patch serviceaccount orders-api -n orders \
-p '{"imagePullSecrets": [{"name": "regcred"}]}'
Pull secrets are namespaced: a secret in default doesn't help pods in orders. Existing pods don't pick up a patched service account — restart the rollout:
Cloud registries (ECR, Artifact Registry, ACR)¶
Prefer node or workload identity over static pull secrets:
- Amazon ECR: give the node IAM role
ecr:GetAuthorizationTokenand read access to the repositories (for example theAmazonEC2ContainerRegistryReadOnlymanaged policy). ECR authorization tokens expire after 12 hours, so hand-created docker-registry secrets fromaws ecr get-login-passwordstop working. - Google Artifact Registry and Azure Container Registry: grant the node or cluster identity reader access to the registry.
Rate limits¶
Authenticate pulls, mirror images into your own registry, or use a pull-through cache (for example ECR pull-through cache rules for Docker Hub). Pin images by digest and avoid imagePullPolicy: Always on tags that rarely change, so nodes reuse cached layers.
Architecture mismatch¶
docker buildx build --platform linux/amd64,linux/arm64 \
-t registry.example.com/orders-api:2.14.0 --push .
Or schedule the workload onto matching nodes with nodeSelector: {kubernetes.io/arch: amd64} until a multi-platform image exists.
Network and DNS¶
From a debug pod on the affected node, test resolution and connectivity to the registry:
In private subnets, pulls need a NAT gateway or registry VPC endpoints (for ECR: the ecr.api, ecr.dkr, and S3 gateway endpoints). See AWS VPC Networking.
Prevention¶
- Deploy immutable tags or digests produced by the same CI run that built them.
- Attach pull credentials through service accounts or node identity, not per-Deployment copies.
- Mirror third-party images into a registry you control.
- Build multi-platform images whenever clusters mix
amd64andarm64nodes. - Add an admission policy that only allows approved registries — see Image and Supply Chain Security.
Production Considerations¶
- Rollouts protect you here. With
maxUnavailable: 0, old pods keep serving while new pods fail to pull. Roll back or fix the tag; don't scale old ReplicaSets manually. - Node replacement exposes hidden dependencies. Images cached on old nodes mask missing credentials until a new node pulls for the first time — often during an autoscaling event, when you can least afford it.
- Watch for registry outages. Alert on events with reason
Failedand messageFailed to pull imageacross the cluster, not per pod.
Related Problems¶
- Pod stuck in Pending — not scheduled yet, so no pull is attempted
- CrashLoopBackOff — the image pulled and started, then exited
- Init container failures —
Init:ImagePullBackOfffor init container images - Container Images: layers, tags, and digests
- All pod startup errors