| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Bash-compatible Linux or macOS shell with kubectl configured; any compatible Kubernetes cluster |
| Cert prep | CKA · CKAD · CKS |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Node access | May be required when registry DNS, TLS trust, proxy settings, or container-runtime pulls must be tested from the assigned worker |
| Scope | ErrImagePull versus ImagePullBackOff, event-driven diagnosis with describe and events, image reference verification, manifest unknown and not found, private registry credentials and imagePullSecrets routing, imagePullPolicy effects, platform and manifest errors, TLS and DNS error patterns, rate limits, verification steps, and error-to-fix tables. Does not cover building images in depth, full registry installation, container-runtime trust store setup, general node networking, CrashLoopBackOff after pull succeeds, application startup debugging, or image signing and admission policy. |
| Related guides | kubectl logs, Events and describe |
ErrImagePull, ImagePullBackOff, and ErrImageNeverPull when policy blocks a pull. It does not cover containers that pulled successfully and then crash; use Fix Kubernetes CrashLoopBackOff for exit codes and restart loops. When STATUS could be scheduling, volume mount, or image pull, start with the Kubernetes Pod troubleshooting workflow first.
The container never started, so there are no application logs to read. Pod events carry the exact registry or runtime error. This walkthrough creates deliberate pull failures in imagepull-lab, reads those events, and maps each message type to the fix you apply on the node that actually pulls the image—not only on your laptop.
Quick reference
There are no application logs during a pull failure. Read events on the assigned worker node. Replace POD and NS with your Pod name and namespace.
| Step | Command | What to check |
|---|---|---|
| 1 | kubectl get pod POD -n NS -o wide |
NODE — the kubelet on that worker performs the pull, not your laptop |
| 2 | kubectl events -n NS --for pod/POD |
The Failed to pull image line is the primary signal — see inspect the error |
| 3 | kubectl describe pod POD -n NS | grep -A8 '^ app:' |
Image name, tag, and imagePullPolicy the kubelet is using |
| 4 | Match the event text to a fix category | not found → image reference; denied → imagePullSecrets; x509 → TLS trust on the node; DNS errors → node resolver — see error-to-fix table |
| 5 | kubectl get pod POD -n NS -w |
After the fix, STATUS should move to Running — see verify the fix |
ErrImagePull vs ImagePullBackOff
ErrImagePull means the kubelet tried to pull the image and the attempt failed. The event message includes the registry response: not found, pull access denied, x509 certificate errors, or DNS failures.
ImagePullBackOff means Kubernetes is delaying further pull attempts after repeated failures. The container sits in Waiting with reason ImagePullBackOff while the kubelet backs off. STATUS in kubectl get often shows ImagePullBackOff or briefly ErrImagePull between retries.
Neither status is a Pod phase. Phase is often Pending while the image is missing, or can show other values depending on cluster version and container state reporting. The actionable detail is always the Failed event text and the Image field in describe—not the STATUS label alone.
Prepare the Image-Pull Lab
Create two Pods with bad image references: a missing tag and an invalid repository path.
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Namespace
metadata:
name: imagepull-lab
---
apiVersion: v1
kind: Pod
metadata:
name: bad-tag
namespace: imagepull-lab
spec:
containers:
- name: app
image: nginx:9.99.99-notexist
---
apiVersion: v1
kind: Pod
metadata:
name: bad-name
namespace: imagepull-lab
spec:
containers:
- name: app
image: nginx/not-a-valid-path:1.27.0
YAMLBoth Pods schedule to a worker immediately; the kubelet fails on the first pull within seconds.
Inspect the Image-Pull Error
Application logs do not exist yet. Wait until each Pod reports a pull failure, then check STATUS and node placement.
for POD in bad-tag bad-name; do
until REASON=$(kubectl get pod "$POD" \
-n imagepull-lab \
-o jsonpath='{.status.containerStatuses[0].state.waiting.reason}' 2>/dev/null) &&
[[ "$REASON" == "ErrImagePull" || "$REASON" == "ImagePullBackOff" ]]; do
sleep 2
done
donekubectl get pods -n imagepull-lab -o wideImagePullBackOff represents a waiting container after failed pulls, with increasingly delayed retries.
Sample output:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
bad-name 0/1 ImagePullBackOff 0 25s 192.168.5.8 worker01 <none> <none>
bad-tag 0/1 ImagePullBackOff 0 25s 192.168.5.11 worker01 <none> <none>RESTARTS stays 0 because the container never ran. Note NODE—pull and DNS checks must succeed from that worker, not only from your workstation.
Read events for the missing-tag Pod:
kubectl events -n imagepull-lab --for pod/bad-tagSample output:
LAST SEEN TYPE REASON OBJECT MESSAGE
25s Normal Scheduled Pod/bad-tag Successfully assigned imagepull-lab/bad-tag to worker01
21s Normal BackOff Pod/bad-tag Back-off pulling image "nginx:9.99.99-notexist"
21s Warning Failed Pod/bad-tag Error: ImagePullBackOff
6s (x2 over 24s) Normal Pulling Pod/bad-tag Pulling image "nginx:9.99.99-notexist"
4s (x2 over 22s) Warning Failed Pod/bad-tag Failed to pull image "nginx:9.99.99-notexist": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/nginx:9.99.99-notexist": failed to resolve image: docker.io/library/nginx:9.99.99-notexist: not found
4s (x2 over 22s) Warning Failed Pod/bad-tag Error: ErrImagePullThe Failed to pull image line is the primary signal. ErrImagePull and ImagePullBackOff are follow-on reasons from the same root failure.
Confirm the image string the kubelet is using:
kubectl describe pod bad-tag -n imagepull-lab | grep -A8 '^ app:'Sample output:
app:
Container ID:
Image: nginx:9.99.99-notexist
Image ID:
Port: <none>
Host Port: <none>
State: Waiting
Reason: ImagePullBackOff
Ready: Falsekubectl logs fails because the container never started:
kubectl logs bad-tag -n imagepull-lab 2>&1 | head -1Sample output:
Error from server (BadRequest): container "app" in pod "bad-tag" is waiting to start: trying and failing to pull imageVerify the Image Reference
Before changing credentials or firewall rules, confirm the full reference the Pod spec sends to the kubelet. A complete private-registry example looks like:
registry.example.com/team/application:1.2.0Check each segment:
- Registry hostname (omit for default
docker.io/library/paths) - Repository path and project or namespace segment
- Image name
- Tag or digest (
@sha256:…) - Letter case where the registry treats names as case-sensitive
- Accidental whitespace or wrong environment variable substitution in CI templates
Compare the live Pod spec to what you pushed:
kubectl get pod bad-tag -n imagepull-lab -o jsonpath='{.spec.containers[0].image}{"\n"}'Sample output:
nginx:9.99.99-notexistUnqualified nginx:tag resolves to docker.io/library/nginx:tag on most clusters. A typo in tag or path surfaces as not found in events.
Fix not found or manifest unknown
When events include not found, manifest unknown, or name unknown, the registry is reachable, but the tag or repository does not exist.
Verify:
- The repository path exists in the registry UI or API
- The tag was pushed to that repository—not only to a fork or alternate project
- The Deployment or StatefulSet template matches the image you tested locally
- CI promoted the digest you expect, not an empty or stale tag
Fix the tag or repository in the controller template, then let the controller recreate Pods or run kubectl rollout restart when needed. Building, tagging, and pushing images is covered in Kubernetes container images.
Apply a corrected Pod to confirm the pull path:
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: good-image
namespace: imagepull-lab
spec:
containers:
- name: app
image: nginx:1.27.0
YAMLkubectl wait --for=condition=Ready pod/good-image -n imagepull-lab --timeout=120s
kubectl get pod good-image -n imagepull-labSample output:
NAME READY STATUS RESTARTS AGE
good-image 1/1 Running 0 8sEvents on the fixed Pod show Pulled, Created, and Started—the pull failure class is resolved.
Fix Private Registry Authentication
Check the Pull Secret
Private registries return pull access denied, unauthorized, or authorization failed: no basic auth credentials when the kubelet has no valid credentials.
Check:
- A pull Secret exists in the Pod namespace
- Secret type is
kubernetes.io/dockerconfigjson(or legacykubernetes.io/dockercfg) imagePullSecretslists the correct Secret name on the Pod or ServiceAccount- Username, token, or password is still valid
- The
docker-serverhostname in the Secret matches the hostname in the image reference (registry.example.comvsregistry.example.com:5000)
Pull Secrets must exist in the Pod namespace and use a supported Docker-config Secret type.
The full setup—creating regcred, attaching it to a ServiceAccount, and testing authenticated pulls—is in Private registry image pulls. This article stops at recognizing auth errors in events and confirming Secret name, namespace, and hostname alignment.
Fix Unauthorized or Access-Denied Errors
When credentials are present but rejected:
- Confirm the account has pull permission on the repository
- Rotate expired tokens or robot passwords
- Match the registry host and optional port in the Secret to the image reference, such as
registry.example.com:5000 - Verify the Pod references the intended Secret—not a similarly named Secret in another namespace
- For cloud registries, check repository policy and IAM or token scope
A reachable registry with missing credentials often shows:
Failed to pull image "192.168.56.108:5000/web:v1.0.0": ... authorization failed: no basic auth credentials
Error: ErrImagePullAdding the correct imagePullSecrets entry clears that message on the next pull attempt.
Check imagePullPolicy
imagePullPolicy controls whether the kubelet contacts the registry on start:
| Policy | Behavior |
|---|---|
Always |
Pull (or verify) from the registry every time the container starts |
IfNotPresent |
Pull only when no local image with that name and tag exists on the node |
Never |
Never pull; fail if the image is not already on the node |
IfNotPresent can mask a bad tag on one node while another node without the image fails with pull errors. Never produces ErrImageNeverPull when the image is absent locally. With Never, the kubelet uses a local image when present and fails only when it is absent. Use a deliberately unique local-only reference so the failure is reproducible:
kubectl apply -f - <<'YAML'
apiVersion: v1
kind: Pod
metadata:
name: never-pull
namespace: imagepull-lab
spec:
containers:
- name: app
image: imagepull-lab.invalid/never-pull:unique-v1
imagePullPolicy: Never
YAMLkubectl wait pod/never-pull \
-n imagepull-lab \
--for=jsonpath='{.status.containerStatuses[0].state.waiting.reason}'=ErrImageNeverPull \
--timeout=60s
kubectl get pod never-pull -n imagepull-labSample output:
NAME READY STATUS RESTARTS AGE
never-pull 0/1 ErrImageNeverPull 0 15sDo not switch policy to Never or IfNotPresent merely to hide registry or tagging mistakes—fix the reference or credentials instead.
Fix Architecture and Platform Errors
Nodes pull only manifests that list a compatible platform. When no manifest matches the node OS or architecture, events may report no matching manifest for linux/amd64 or similar.
Check:
- Node OS and architecture labels:
kubectl get nodes -L kubernetes.io/os,kubernetes.io/archOr inspect the assigned node for one failing Pod:
NODE=$(kubectl get pod bad-tag -n imagepull-lab -o jsonpath='{.spec.nodeName}')
kubectl get node "$NODE" -o jsonpath='os={.metadata.labels.kubernetes\.io/os} arch={.metadata.labels.kubernetes\.io/arch}{"\n"}'- Platforms the image publishes (
docker manifest inspector registry UI) - Whether you need a multi-arch build or an arch-specific tag
If the image pulls but the container fails with exec format error, the binary targets the wrong architecture—that is a startup failure after a successful pull, not ImagePullBackOff. Route those cases to crash-loop and command troubleshooting, not registry auth.
Fix Node-to-Registry Connectivity
Registry TLS Errors
TLS problems appear in pull events before the container starts. Common messages:
x509: certificate signed by unknown authorityx509: certificate is valid for …, not …(hostname mismatch)- Certificate expired errors
Check:
- Registry certificate chain is complete and not expired
- Image reference hostname matches the certificate SAN
- Nodes trust the issuing CA (system store or runtime-specific config)
- Do not include
http://orhttps://in the Pod image field. Use only the registry hostname, optional port, repository, and tag or digest. Configure HTTPS trust or an intentionally insecure registry in the node's container runtime.
Full private-CA installation and container-runtime trust configuration are out of scope here. Use the event text to decide whether to fix the certificate, the hostname in the image string, or node trust—not application code.
Registry DNS and Network Errors
Pull failures from DNS or TCP show messages such as no such host, connection timeouts, or dial tcp errors. The registry must be reachable from the assigned node.
Check from the worker when possible:
nslookupordigfor the registry hostname- TCP connectivity to the registry port
- Corporate proxy settings on nodes
- Firewall paths between node subnets and the registry
- Registry service health and load balancer backends
Successful docker pull or crictl pull from your laptop does not prove worker nodes can reach the same hostname. Always test from the node listed in kubectl get pod -o wide.
Registry Rate Limits
When events mention rate limiting or too many requests:
- Authenticate pulls—anonymous limits are often lower
- Reduce unnecessary pulls (
Alwayson many replicas with mutable tags) - Use immutable version tags or digests instead of constantly moving
latest - Review registry quota and CI retry storms
This is not a product comparison—match the event to auth and pull frequency before opening vendor dashboards.
Verify the Fix
After correcting the image string, Secret, or network path, watch events transition to success:
PullingthenPulledfor the expected image referenceCreatedandStartedon the container- Pod
READYmatches the number of containers - Deployment
kubectl rollout statuscompletes when a controller owns the Pod
On the lab fix Pod:
kubectl describe pod good-image -n imagepull-lab | grep -A6 '^Events:'Sample output:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8s default-scheduler Successfully assigned imagepull-lab/good-image to worker01
Normal Pulled 8s kubelet spec.containers{app}: Container image "nginx:1.27.0" already present on machine and can be accessed by the pod
Normal Created 7s kubelet spec.containers{app}: Container created
Normal Started 7s kubelet spec.containers{app}: Container startedDelete a standalone failed Pod only after you fix the manifest or when you want a controller to recreate it with the updated template. Deleting without fixing the Deployment template recreates the same bad pull.
Error-to-Fix Table
| Event message | Check first |
|---|---|
not found / manifest unknown |
Repository path, tag, and digest in the Pod spec |
pull access denied / unauthorized |
Registry credentials, token expiry, repository permissions |
no basic auth credentials |
imagePullSecrets name, namespace, and Secret type |
x509 certificate errors |
Registry cert, hostname in image, node or runtime trust |
| DNS lookup failure | Node DNS and registry hostname spelling |
no matching manifest |
Image platforms versus node kubernetes.io/arch |
| Rate limit exceeded | Authentication, tag immutability, pull frequency |
ErrImageNeverPull |
imagePullPolicy: Never with missing local image |
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
kubectl logs returns waiting to pull |
Container never started | Read describe events; ignore logs until pull succeeds |
| Pull works on laptop, fails on cluster | Node network or DNS differs | Test from assigned worker node |
| Only some nodes fail | IfNotPresent and stale local image on healthy nodes |
Fix the image reference and use an immutable tag or digest; recreate the failed Pods after correcting the controller template |
| Secret exists but pull still denied | Wrong docker-server hostname in Secret |
Match host and port to image reference exactly |
ImagePullBackOff after registry outage |
Backoff timer still active | Fix registry; wait for retry or delete Pod after fix |
| Wrong arch after pull | Multi-arch manifest missing platform | Publish correct arch or use arch-specific tag |
What's Next
- Fix Kubernetes Pods Stuck in Pending or ContainerCreating
- Fix Kubernetes OOMKilled and Exit Code 137
- Delete a Stuck or Terminating Kubernetes Pod
References
- Images — image names, pull policies, and pull Secrets
- Pull an Image from a Private Registry — official imagePullSecrets workflow
- Determine the reason for Pod failure — interpreting exit and waiting reasons
Summary
Image pull failures never produce application logs—the kubelet stops before the container starts. ErrImagePull marks a failed attempt; ImagePullBackOff means Kubernetes is waiting before retrying. The fix lives in Pod events: the Failed to pull image line names the registry response.
In imagepull-lab, a missing nginx tag produced not found and exit from kubectl logs, while correcting the tag produced Pulled and Started on worker01. Match the message to the category—wrong reference, missing credentials, TLS, DNS, platform manifest, or policy—before tuning unrelated probe or crash-loop settings.
Private registry setup and imagePullSecrets wiring belong in the dedicated registry guide; building and pushing images belong in the container images guide. Once the image pulls, if the container still restarts, switch to CrashLoopBackOff troubleshooting—the pull problem is already solved.

