Fix Kubernetes ImagePullBackOff for Private Registries: Create and Use imagePullSecrets
Quick Answer
- Conclusion: ImagePullBackOff for private registry images is almost always an authentication failure. The fix is to create a
docker-registrySecret (regcred) and reference it in the Pod'simagePullSecretsfield. - First checks: Run
kubectl describe pod <pod-name>and look forFailedToRetrieveImagePullSecretorpull access deniedevents. Verify the Secret exists in the same namespace as the Pod withkubectl get secret -n <namespace>. - Minimal fix: Create the Secret with
kubectl create secret docker-registry regcred --docker-server=<registry-fqdn> --docker-username=<user> --docker-password=<pass> --docker-email=<email>, then addimagePullSecretsto the Pod spec. - Version boundary: This applies to all Kubernetes versions that support
imagePullSecrets(v1.0+). For Docker Hub, the--docker-servervalue must behttps://index.docker.io/v1/— notdocker.ioorindex.docker.io.
What Problem It Solves
When a Kubernetes Pod references an image from a private registry, the kubelet must authenticate before pulling. Without credentials, the container runtime rejects the pull and the Pod enters ImagePullBackOff or ErrImagePull status.
The standard solution is a kubernetes.io/dockerconfigjson Secret (created via kubectl create secret docker-registry). This Secret stores the registry endpoint, username, password, and email. You then attach it to the Pod spec so the kubelet can authenticate.
This pattern works for Docker Hub, self-hosted registries (Harbor, Nexus, GitLab), and cloud registries (ECR, GCR, ACR) — though cloud registries often use short-lived tokens rather than static passwords.
When This Error or Setup Appears
You will hit this issue when:
- Your Pod spec references an image like
myregistry.example.com/app:latestor a private Docker Hub image. - The Pod status shows
ImagePullBackOfforErrImagePull. kubectl describe podshows an event like:
Failed to pull image "myregistry.example.com/app:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied for myregistry.example.com/app, repository does not exist or may require 'docker login'
This error message is misleading — the repository may exist, but the kubelet lacks credentials.
Minimal Working Configuration
Step 1: Create the Secret
BASHkubectl create secret docker-registry regcred \ --docker-server=https://index.docker.io/v1/ \ --docker-username=<your-username> \ --docker-password=<your-password> \ --docker-email=<your-email>
For a private registry, replace --docker-server with your registry FQDN:
BASHkubectl create secret docker-registry regcred \ --docker-server=harbor.example.com \ --docker-username=<your-username> \ --docker-password=<your-password> \ --docker-email=<your-email>
Step 2: Reference the Secret in the Pod Spec
YAMLapiVersion: v1 kind: Pod metadata: name: private-image-pod spec: containers: - name: app image: your-private-registry.example.com/app:latest imagePullSecrets: - name: regcred
Step 3: Verify
BASHkubectl apply -f pod.yaml kubectl get pods kubectl describe pod private-image-pod
The Pod should transition to Running if the credentials are valid and the image exists.
Parameters and Environment Variables
The kubectl create secret docker-registry command accepts four required parameters:
| Parameter | Required | Description |
|---|---|---|
--docker-server | Yes | Registry FQDN. Use https://index.docker.io/v1/ for Docker Hub. |
--docker-username | Yes | Registry username. |
--docker-password | Yes | Registry password or access token. |
--docker-email | Yes | Email associated with the registry account. |
Important: For Docker Hub, the server value must be exactly https://index.docker.io/v1/. Using docker.io or index.docker.io without the scheme and path will cause authentication failures.
Root Cause Analysis
The ImagePullBackOff loop has three common root causes:
-
Missing Secret: The Pod references
imagePullSecretsbut the Secret does not exist in the Pod's namespace. The kubelet emitsFailedToRetrieveImagePullSecretand proceeds without credentials, which fails. -
Wrong namespace: The Secret exists but in a different namespace than the Pod. Secrets are namespace-scoped; the kubelet cannot use a Secret from another namespace.
-
Invalid credentials: The username/password pair is wrong, expired, or the account lacks pull permission for the repository. The runtime returns
pull access denied. -
Wrong
--docker-servervalue: For Docker Hub, usingdocker.ioinstead ofhttps://index.docker.io/v1/results in the kubelet looking for credentials under the wrong key in the Secret'sconfig.json.
Common Errors and Fixes
| Error | Fix |
|---|---|
pull access denied for <image>, repository does not exist or may require 'docker login' | Verify the image name is correct. Confirm the Secret exists and is in the same namespace as the Pod. Check credentials with docker login manually. |
FailedToRetrieveImagePullSecret: Unable to retrieve some image pull secrets (<regcred>) | The Secret name in imagePullSecrets does not match an existing Secret in the Pod's namespace. Run kubectl get secret -n <namespace> to list available Secrets. |
Error from server (BadRequest): error when creating "pod.yaml": Secret "regcred" not found | Create the Secret before applying the Pod manifest. The Secret must exist at apply time. |
error: no objects passed to create | Check the --docker-server value. It must be a complete registry address. For Docker Hub, use https://index.docker.io/v1/. |
Production Notes and Security Checks
-
Avoid plaintext passwords in shell history. Use
docker loginto generate~/.docker/config.json, then create the Secret from that file:BASHkubectl create secret generic regcred \ --from-file=.dockerconfigjson=$HOME/.docker/config.json \ --type=kubernetes.io/dockerconfigjsonAlternatively, pass the password via an environment variable:
BASHkubectl create secret docker-registry regcred \ --docker-server=harbor.example.com \ --docker-username=ci-bot \ --docker-password="$REGISTRY_PASSWORD" \ --docker-email=ci@example.com -
Use access tokens instead of account passwords where the registry supports them (Docker Hub access tokens, Harbor robot accounts, cloud IAM tokens). This limits blast radius and enables per-service revocation.
-
Automate injection via ServiceAccount. Patch the default ServiceAccount so all new Pods automatically get the Secret:
BASHkubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "regcred"}]}'Or create a dedicated ServiceAccount and reference it in the Pod spec.
-
Rotate credentials regularly. Update the Secret with new credentials and restart affected workloads. For multi-cluster environments, create the Secret in each cluster separately.
-
Check network policies. Ensure the kubelet can reach the registry endpoint. A restrictive
NetworkPolicycan block egress to the registry, causing pull timeouts that surface asImagePullBackOff. -
Namespace isolation. The Secret and Pod must share the same namespace. For multi-tenant clusters, create per-namespace Secrets rather than sharing one.
FAQ
Q: How do I configure imagePullSecrets for multiple private registries?
A: Add multiple entries to the imagePullSecrets list, one per registry:
YAMLimagePullSecrets: - name: regcred-dockerhub - name: regcred-harbor
Kubernetes tries each Secret in order until one authenticates successfully.
Q: How do I avoid exposing the Docker password in the command line?
A: Use docker login to generate ~/.docker/config.json, then create the Secret with --from-file=.dockerconfigjson. Or pass the password through an environment variable or a secrets manager like Vault. Avoid embedding plaintext passwords in CI logs or shell history.
Q: How do I automatically inject imagePullSecrets into all Pods?
A: Patch the default ServiceAccount in the namespace:
BASHkubectl patch serviceaccount default -p '{"imagePullSecrets": [{"name": "regcred"}]}'
New Pods that use the default ServiceAccount will automatically include the Secret. For selective injection, create a custom ServiceAccount and reference it in the Pod spec.