Fix Kubernetes ImagePullBackOff for Private Registries: Create and Use imagePullSecrets

Topic: kubernetes-imagepullbackoff-private-registryUpdated 8/2/2026

Quick Answer

  • Conclusion: ImagePullBackOff for private registry images is almost always an authentication failure. The fix is to create a docker-registry Secret (regcred) and reference it in the Pod's imagePullSecrets field.
  • First checks: Run kubectl describe pod <pod-name> and look for FailedToRetrieveImagePullSecret or pull access denied events. Verify the Secret exists in the same namespace as the Pod with kubectl 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 add imagePullSecrets to the Pod spec.
  • Version boundary: This applies to all Kubernetes versions that support imagePullSecrets (v1.0+). For Docker Hub, the --docker-server value must be https://index.docker.io/v1/ — not docker.io or index.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:latest or a private Docker Hub image.
  • The Pod status shows ImagePullBackOff or ErrImagePull.
  • kubectl describe pod shows 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

BASH
kubectl 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:

BASH
kubectl 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

YAML
apiVersion: 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

BASH
kubectl 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:

ParameterRequiredDescription
--docker-serverYesRegistry FQDN. Use https://index.docker.io/v1/ for Docker Hub.
--docker-usernameYesRegistry username.
--docker-passwordYesRegistry password or access token.
--docker-emailYesEmail 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:

  1. Missing Secret: The Pod references imagePullSecrets but the Secret does not exist in the Pod's namespace. The kubelet emits FailedToRetrieveImagePullSecret and proceeds without credentials, which fails.

  2. 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.

  3. Invalid credentials: The username/password pair is wrong, expired, or the account lacks pull permission for the repository. The runtime returns pull access denied.

  4. Wrong --docker-server value: For Docker Hub, using docker.io instead of https://index.docker.io/v1/ results in the kubelet looking for credentials under the wrong key in the Secret's config.json.

Common Errors and Fixes

ErrorFix
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 foundCreate the Secret before applying the Pod manifest. The Secret must exist at apply time.
error: no objects passed to createCheck 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 login to generate ~/.docker/config.json, then create the Secret from that file:

    BASH
    kubectl create secret generic regcred \
      --from-file=.dockerconfigjson=$HOME/.docker/config.json \
      --type=kubernetes.io/dockerconfigjson
    

    Alternatively, pass the password via an environment variable:

    BASH
    kubectl 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:

    BASH
    kubectl 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 NetworkPolicy can block egress to the registry, causing pull timeouts that surface as ImagePullBackOff.

  • 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:

YAML
imagePullSecrets:
  - 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:

BASH
kubectl 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.

Official References

Related Guides