Containers Advanced

Kubernetes ImagePullBackOff: Resolving Secret Authentication Issues with Private Registries on Ubuntu 22.04 LTS

Troubleshoot and fix Kubernetes ImagePullBackOff errors caused by private registry authentication failures using image pull secrets on Ubuntu 22.04 LTS clusters.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Troubleshoot and fix Kubernetes ImagePullBackOff errors caused by private registry authentication failures using image pull secrets on Ubuntu 22.04 LTS clusters.

When deploying applications to Kubernetes that rely on private container image registries, encountering an ImagePullBackOff error is a common hurdle for many DevOps engineers. This specific guide focuses on ImagePullBackOff scenarios where the underlying cause is an authentication failure with a private registry. This typically means Kubernetes cannot log into your registry to retrieve the necessary image, leading to your pods being stuck in a Pending or ErrImagePull state. For high-availability, production-grade deployments on Ubuntu 22.04 LTS, understanding and correctly configuring Kubernetes imagePullSecrets is paramount.

Symptom & Error Signature

Your Kubernetes pods will fail to start, remaining in a Pending or CrashLoopBackOff state, with the STATUS indicating ImagePullBackOff or ErrImagePull.

You can observe these symptoms using kubectl:

kubectl get pods -n my-namespace
NAME                             READY   STATUS             RESTARTS      AGE
my-app-deployment-7b8c7f9d-abcd1   0/1     ImagePullBackOff   0             2m

Detailed error messages can be found by describing the problematic pod or checking Kubernetes events:

kubectl describe pod my-app-deployment-7b8c7f9d-abcd1 -n my-namespace
...
Events:
  Type     Reason                 Age    From               Message
  ----     ------                 ----   ----               -------
  Normal   Scheduled              2m     default-scheduler  Successfully assigned my-namespace/my-app-deployment-7b8c7f9d-abcd1 to k8s-worker-01
  Normal   Pulling                1m     kubelet            Pulling image "your.private.registry.com/my-app:latest"
  Warning  Failed                 1m     kubelet            Failed to pull image "your.private.registry.com/my-app:latest": rpc error: code = Unknown desc = Error response from daemon: Get "https://your.private.registry.com/v2/": unauthorized: authentication required
  Warning  Failed                 1m     kubelet            Error: ErrImagePull
  Normal   BackOff                50s    kubelet            Back-off pulling image "your.private.registry.com/my-app:latest"
  Warning  Failed                 50s    kubelet            Error: ImagePullBackOff

Alternatively, you can filter events more broadly:

kubectl get events -n my-namespace --field-selector type=Warning
LAST SEEN   TYPE      REASON             OBJECT                                 MESSAGE
2m          Warning   Failed             pod/my-app-deployment-7b8c7f9d-abcd1   Failed to pull image "your.private.registry.com/my-app:latest": rpc error: code = Unknown desc = Error response from daemon: Get "https://your.private.registry.com/v2/": unauthorized: authentication required

The key message here is unauthorized: authentication required, explicitly pointing to a credentials issue.

Root Cause Analysis

The ImagePullBackOff error with an "unauthorized: authentication required" message clearly indicates that the Kubernetes worker node's container runtime (typically Docker or containerd) could not authenticate with the specified private image registry.

The common underlying reasons include:

  1. Missing or Incorrect imagePullSecret: Kubernetes uses imagePullSecrets to pass registry credentials to the Kubelet on worker nodes. If this secret is missing, named incorrectly, or contains invalid credentials, authentication will fail.
  2. Invalid Credential Format within the Secret: The imagePullSecret stores a base64-encoded config.json file. Errors can arise if this file is malformed or the credentials (username, password, auth token) within it are incorrect or expired.
  3. Secret Not Linked to Pod/Deployment: Even if the imagePullSecret exists and is correct, the Pod or Deployment specification must explicitly reference it using the imagePullSecrets field.
  4. Registry URL Mismatch: The registry URL specified in the image name (e.g., your.private.registry.com/my-app:latest) must exactly match the server defined in the imagePullSecret.
  5. Network Connectivity Issues: While less common for explicit unauthorized errors, network blocks or DNS resolution failures preventing access to the registry can manifest in similar ways, though often with different error messages (e.g., connection refused, timeout).
  6. Registry Certificate Issues: If your private registry uses self-signed certificates or certificates from a private Certificate Authority (CA), the worker nodes' Docker daemon might not trust these certificates, leading to communication failures before authentication can even occur. Although typically a TLS handshake error, it can sometimes indirectly contribute to authentication issues if the initial secure connection cannot be established.

Step-by-Step Resolution

Follow these steps meticulously to diagnose and resolve the ImagePullBackOff issue due to secret authentication problems.

1. Verify Pod Status and Events

Start by re-confirming the error signature and obtaining detailed logs.

kubectl get pods -n <your-namespace>
kubectl describe pod <problematic-pod-name> -n <your-namespace>
kubectl get events -n <your-namespace> --field-selector involvedObject.name=<problematic-pod-name>

Ensure the message explicitly states unauthorized: authentication required. This confirms the problem lies with credentials.

2. Inspect and Decode the imagePullSecret

First, check if the imagePullSecret exists and its name matches what your Deployment/Pod spec intends to use. Let's assume your secret is named my-registry-secret.

kubectl get secret my-registry-secret -n <your-namespace> -o yaml

You should see an output similar to this:

apiVersion: v1
data:
  .dockerconfigjson: eyJhdXRocyI6eyJ5b3VyLnByaXZhdGUucmVnaXN0cnkuY29tIjp7InVzZXJuYW1lIjoieW91cl91c2VybmFtZSIsInBhc3N3b3JkIjoieW91cl9wYXNzd29yZCIsImVtYWlsIjoieW91cl9lbWFpbEBleGFtcGxlLmNvbSJ9fX0=
kind: Secret
metadata:
  creationTimestamp: "2023-10-26T10:00:00Z"
  name: my-registry-secret
  namespace: my-namespace
  resourceVersion: "12345"
  uid: a1b2c3d4-e5f6-7890-1234-567890abcdef
type: kubernetes.io/dockerconfigjson

The critical part is the data..dockerconfigjson field. This is a base64-encoded string representing your ~/.docker/config.json file. Decode it to inspect the credentials:

kubectl get secret my-registry-secret -n <your-namespace> -o jsonpath='{.data..dockerconfigjson}' | base64 -d | jq .

If jq is not installed, install it with sudo apt update && sudo apt install -y jq.

This should output a JSON structure like:

{
  "auths": {
    "your.private.registry.com": {
      "username": "your_username",
      "password": "your_password",
      "email": "[email protected]",
      "auth": "Y2xlaW50OnBhc3N3b3Jk" # This is base64(username:password)
    }
  }
}

Verify that the registry.com URL, username, password, and auth token (if present) are absolutely correct and match your private registry credentials. The auth field is usually preferred by Docker/Kubernetes if present.

3. Link the imagePullSecret to the Pod/Deployment

Ensure your Pod or Deployment specification correctly references the imagePullSecret. This is done under spec.template.spec for Deployments:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
  namespace: my-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      # --- IMPORTANT: Ensure this section is present and correct ---
      imagePullSecrets:
      - name: my-registry-secret # This name MUST match your secret
      # -----------------------------------------------------------
      containers:
      - name: my-container
        image: your.private.registry.com/my-app:latest # Ensure this registry matches the secret's registry
        ports:
        - containerPort: 80

A common mistake is using a different name for the imagePullSecrets entry than the actual secret, or forgetting to include this section entirely.

4. Test Registry Access Manually from a Worker Node

This step is crucial for isolating whether the problem is Kubernetes-specific or a fundamental issue with the credentials or registry access from the underlying host.

SSH into one of your Kubernetes worker nodes (e.g., k8s-worker-01 from the kubectl describe pod output):

ssh k8s-worker-01

Once on the worker node, try to log in to your private registry using Docker:

sudo docker login your.private.registry.com

When prompted, enter the exact username and password that should be in your imagePullSecret. If the login is successful, you should see Login Succeeded. Then, try to pull the image manually:

sudo docker pull your.private.registry.com/my-app:latest
  • If docker login fails here: The credentials are incorrect, expired, or there's a problem with the registry itself (e.g., firewall, registry down). You need to resolve this issue with your registry administrator first.
  • If docker login and docker pull succeed: This indicates the credentials are valid, and the problem is specifically within how Kubernetes is using the imagePullSecret. Proceed to the next steps.

5. Recreate or Update the imagePullSecret

If you identified incorrect credentials in Step 2 or if the manual Docker login (Step 4) failed, you need to update your secret. The easiest way is to delete and recreate it with the correct information.

Deleting a secret will immediately affect any pods that rely on it. Ensure you have the correct credentials readily available for recreation.

First, delete the old secret:

kubectl delete secret my-registry-secret -n <your-namespace>

Then, recreate it with the correct credentials. Use the kubectl create secret docker-registry command which correctly formats the config.json for you:

kubectl create secret docker-registry my-registry-secret 
  --docker-server=your.private.registry.com 
  --docker-username=your_correct_username 
  --docker-password=your_correct_password 
  [email protected] 
  -n <your-namespace>

Double-check the --docker-server URL matches the registry you're trying to pull from, and your_correct_username/your_correct_password are verified working.

6. Restart the Deployment/Pod

After updating the imagePullSecret or fixing the Deployment YAML, Kubernetes needs to re-evaluate the image pull process. The simplest way to achieve this for a Deployment is to trigger a rollout restart:

kubectl rollout restart deployment my-app-deployment -n <your-namespace>

For individual pods (if not part of a Deployment, which is less common in production):

kubectl delete pod <problematic-pod-name> -n <your-namespace>

Kubernetes will automatically create a new pod, which will then attempt to pull the image using the refreshed secret. Monitor the status:

kubectl get pods -n <your-namespace> -w

You should see the new pod transition through ContainerCreating and then Running.

7. Address Registry Certificate Issues (if applicable)

If your private registry uses self-signed or internal CA certificates, and you're getting x509: certificate signed by unknown authority errors (or sometimes even unauthorized errors if the initial TLS handshake fails), the worker nodes' Docker daemon needs to trust these certificates.

On each Kubernetes worker node (Ubuntu 22.04 LTS):

  1. Copy the CA certificate:

    sudo mkdir -p /etc/docker/certs.d/your.private.registry.com
    # Replace your-ca-cert.crt with the actual path to your registry's CA certificate
    sudo cp /path/to/your-ca-cert.crt /etc/docker/certs.d/your.private.registry.com/ca.crt
    

    Alternatively, for system-wide trust:

    sudo cp /path/to/your-ca-cert.crt /usr/local/share/ca-certificates/your-registry-ca.crt
    sudo update-ca-certificates
    
  2. Restart Docker service:

    sudo systemctl restart docker
    
  3. Verify from worker node:

    sudo docker login your.private.registry.com
    sudo docker pull your.private.registry.com/my-app:latest
    

If you are using an insecure registry (HTTP or self-signed certs without proper trust setup), you might configure insecure-registries in /etc/docker/daemon.json on each worker node:

# /etc/docker/daemon.json
{
  "insecure-registries": ["your.private.registry.com"]
}

Then, restart Docker: sudo systemctl restart docker.

Using insecure-registries is a significant security risk as it disables TLS verification. Only use this in highly controlled, isolated environments or for testing, and never in production if proper TLS can be configured.

8. Verify Network Connectivity

While less likely for an explicit unauthorized message, a quick check of network connectivity to your registry from a worker node can rule out basic infrastructure issues.

On a worker node:

ping -c 4 your.private.registry.com
curl -v https://your.private.registry.com/v2/ # This should ideally respond with "Unauthorized" or a similar HTTP 401, confirming basic reachability.

If these fail, investigate network firewall rules, DNS resolution, or registry service status.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn →

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.