Kubernetes Error Guide: 'pull access denied' Private Registry Auth Failure
Fix 'pull access denied, repository does not exist or may require docker login' in Kubernetes by wiring a working imagePullSecret to the pod's service account.
- #kubernetes-helm
- #troubleshooting
- #errors
- #registry
Exact Error Message
When the kubelet tries to pull from a private registry without valid credentials, the pull fails with pull access denied and the pod enters ErrImagePull then ImagePullBackOff:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 20s (x3 over 58s) kubelet Pulling image "registry.acme.io/payments/api:1.4.0"
Warning Failed 19s (x3 over 57s) kubelet Failed to pull image "registry.acme.io/payments/api:1.4.0": rpc error: code = Unknown desc = failed to pull and unpack image "registry.acme.io/payments/api:1.4.0": failed to resolve reference "registry.acme.io/payments/api:1.4.0": pull access denied, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
Warning Failed 19s (x3 over 57s) kubelet Error: ErrImagePull
Normal BackOff 5s (x4 over 56s) kubelet Back-off pulling image "registry.acme.io/payments/api:1.4.0"
The canonical CLI form is:
Error response from daemon: pull access denied for registry.acme.io/payments/api, repository does not exist or may require 'docker login'
The phrase may require 'docker login' is the giveaway: the registry refused the request because the kubelet presented no credentials or invalid ones.
What the Error Means
Private registries refuse anonymous reads. They return a generic denied to anyone who is not authenticated — deliberately ambiguous so attackers cannot distinguish “this repo does not exist” from “you are not allowed in.” Kubernetes surfaces that ambiguity verbatim: repository does not exist or may require docker login.
In a cluster, the kubelet does not have your laptop’s ~/.docker/config.json. It obtains registry credentials from an imagePullSecret of type kubernetes.io/dockerconfigjson, which must be referenced either by the pod spec (imagePullSecrets) or by the pod’s service account. If no such secret is attached, or it holds wrong/expired credentials, or it points at a different registry host, the kubelet pulls anonymously and the registry says denied.
So this error is almost always a credentials-wiring problem, not a missing image. The fix is to create a correct pull secret and attach it where the pod will actually look.
Common Causes
- No imagePullSecret at all — a private image with a pod and service account that reference no pull secret.
- Secret in the wrong namespace — pull secrets are namespaced; a secret in
defaultdoes nothing for a pod inprod. - Wrong registry host in the secret — the
.dockerconfigjsonauthskey must match the image’s registry exactly (registry.acme.iovshttps://registry.acme.io/v1/). - Expired or rotated credentials — a token or robot-account password expired, or the password was rotated without updating the secret.
- Secret not attached — the secret exists but neither the pod nor its service account lists it under
imagePullSecrets. - Wrong secret type — created as
Opaqueinstead ofkubernetes.io/dockerconfigjson, so the kubelet ignores it. - Insufficient scope — the credentials are valid but the robot account/token lacks pull permission on that specific repository.
How to Reproduce the Error
Reference any private image with no pull secret configured:
apiVersion: v1
kind: Pod
metadata:
name: pull-denied-demo
spec:
containers:
- name: app
image: registry.acme.io/payments/api:1.4.0 # private, no secret attached
kubectl apply -f pull-denied-demo.yaml
kubectl get pod pull-denied-demo
NAME READY STATUS RESTARTS AGE
pull-denied-demo 0/1 ImagePullBackOff 0 22s
kubectl describe pod pull-denied-demo shows pull access denied ... may require 'docker login'.
Diagnostic Commands
# Read the exact error and the image reference
kubectl describe pod <POD> | grep -A8 Events
# Does the pod (or its service account) reference any pull secret?
kubectl get pod <POD> -o jsonpath='{.spec.imagePullSecrets}{"\n"}'
kubectl get pod <POD> -o jsonpath='{.spec.serviceAccountName}{"\n"}'
kubectl get sa <SERVICE_ACCOUNT> -o jsonpath='{.imagePullSecrets}{"\n"}'
# Is the secret the right type and in the right namespace?
kubectl get secret <SECRET> -o jsonpath='{.type}{"\n"}'
kubectl get secret <SECRET> -n <NAMESPACE>
# Inspect which registry hosts the secret actually authenticates
kubectl get secret <SECRET> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq '.auths | keys'
# Verify the credentials work from a machine, read-only
skopeo inspect --creds '<USER>:<PASS>' docker://registry.acme.io/payments/api:1.4.0
The jq '.auths | keys' output must contain the exact registry host from the failing image reference.
Step-by-Step Resolution
1. Confirm the registry host. Extract the host from the image reference in the describe output. The pull secret’s auths must key on that exact host.
2. Create a docker-registry secret in the pod’s namespace.
kubectl create secret docker-registry acme-pull \
--docker-server=registry.acme.io \
--docker-username=<ROBOT_USER> \
--docker-password=<ROBOT_TOKEN> \
--namespace=prod
3. Attach it to the pod or the service account. Per-pod:
spec:
imagePullSecrets:
- name: acme-pull
Or cluster-wide for everything using that service account:
kubectl patch serviceaccount default -n prod \
-p '{"imagePullSecrets":[{"name":"acme-pull"}]}'
4. Verify the secret type. It must be kubernetes.io/dockerconfigjson. If you created it as Opaque, delete and recreate with kubectl create secret docker-registry.
5. Test the credentials independently. Run skopeo inspect --creds (above). If that fails too, the credentials themselves are wrong or unscoped — rotate the robot token and grant it pull on the repository.
6. Recreate the pod and watch it pull.
kubectl delete pod <POD>
kubectl get pod <POD> -w
7. For namespace sprawl, automate replication of the pull secret into every namespace that needs it (e.g. a controller or a sync job), since secrets do not cross namespace boundaries.
Prevention and Best Practices
- Attach pull secrets to service accounts, not individual pods, so every workload in a namespace inherits them automatically.
- Use robot/service accounts with read-only scope per repository instead of human credentials, and rotate them on a schedule.
- Replicate the pull secret into every namespace that runs private images — a small sync job prevents the “works in default, fails in prod” trap.
- On managed clusters, prefer the cloud-native identity path (IAM roles for ECR, workload identity for GCR/Artifact Registry) so no static secret exists to expire.
- Alert on
ImagePullBackOffso an expired token surfaces before a scaling event multiplies the failure. - Keep the registry host string consistent between manifests and secrets — no scheme, no trailing path.
Related Errors
- manifest unknown — authorized but the tag/digest does not exist, a different failure.
- ErrImagePull — the immediate pull failure state.
- ImagePullBackOff — the back-off after repeated denials.
- Failed to pull image — general pull-failure triage.
Frequently Asked Questions
Why does the message say the repository might not exist when I know it does? Private registries return a deliberately ambiguous denied to unauthenticated callers so they cannot probe which repos exist. The repository is almost always fine — the kubelet just presented no valid credentials.
I have an imagePullSecret but it is still failing. Why? Three common reasons: the secret is in a different namespace than the pod, the pod/service account does not actually reference it, or the secret’s auths keys on a different registry host than the image. Check all three with the diagnostic commands above.
Does the pull secret need to be in every namespace? Yes. ImagePullSecrets are namespaced and do not propagate. A pod in prod cannot use a secret in default. Replicate the secret into each namespace or attach it via that namespace’s service account.
What is the difference between attaching to a pod vs a service account? A pod-level imagePullSecrets applies only to that pod. Attaching to the service account makes every pod that uses that account inherit the secret automatically — far less error-prone at scale.
Should I use static secrets or cloud IAM? On managed clusters, prefer cloud-native identity (ECR/GCR/ACR workload identity). It removes the static credential entirely, so there is nothing to expire or leak. Static docker-registry secrets are best for self-hosted or third-party registries.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.