Problem
During a routine rollout of the Prometheus monitoring service for AI workloads, the container failed to start with an image pull error. The Kubernetes pod entered ImagePullBackOff and the following log snippet was observed in the node’s kubelet output:
Failed to pull image "registry.example.com/monitoring/prometheus:2.45.0": rpc error: code = Unknown desc = failed to resolve reference "registry.example.com/monitoring/prometheus:2.45.0": unauthorized: authentication required
Simultaneously, the docker daemon on the host reported:
time="2026-11-02T08:12:34Z" level=error msg="Error response from daemon: pull access denied for registry.example.com/monitoring/prometheus, repository does not exist or may require 'docker login': denied: requested access to the resource is denied"
The failure manifested only after the organization rotated the credentials used for the private Docker registry.
Root Cause
The image pull failure was caused by a combination of three factors that surfaced after the credential rotation:
- Stale Kubernetes secret: The
imagePullSecretreferenced by thePrometheusdeployment still contained the previous.dockerconfigjsonpayload. Kubernetes does not automatically refresh secrets when the underlying registry credentials change. - Missing image tag in the manifest: The deployment manifest used the
latesttag implicitly (registry.example.com/monitoring/prometheus) while the registry now requires an explicit tag for authentication scoping. The registry rejected the request because the token generated for the old tag did not match the new policy. - Network ACL change: As part of the credential rotation, a new service‑mesh policy was applied that limited outbound traffic from the
monitoringnamespace to the registry’s IP range. The pod’s egress attempt was blocked, causing a timeout that was later reported as an authentication error.
These issues together produced the observed “unauthorized” messages even though the credentials themselves were valid.
Debug
1. Verify the secret content
# Inspect the secret referenced by the deployment
kubectl get secret prometheus-registry-secret -n monitoring -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d | jq .
Expected output (redacted):
{
"auths": {
"registry.example.com": {
"username": "ci-bot",
"password": "********",
"auth": "Y2ktYm90OnBhc3N3b3Jk"
}
}
}
If the password field still contains the old credential, the secret must be refreshed.
2. Check the image reference in the deployment
kubectl get deployment prometheus -n monitoring -o yaml | grep image
Typical output after rotation:
image: registry.example.com/monitoring/prometheus
Notice the missing tag. The registry now enforces tag‑specific tokens.
3. Test connectivity from a pod in the same namespace
kubectl run -i --tty --rm debug-pod --image=alpine --restart=Never -n monitoring -- sh
apk add --no-cache curl
curl -v https://registry.example.com/v2/_catalog
Expected HTTP 200 with a JSON catalog. A timeout or 403 Forbidden indicates the ACL is blocking egress.
4. Review the service‑mesh policy
istioctl proxy-config listeners $(kubectl get pod -l app=prometheus -n monitoring -o name) -n monitoring
Look for egress rules that restrict traffic to registry.example.com. If missing, the request will be dropped.
Solution
1. Regenerate the image pull secret
Obtain the new registry password from the secret manager and recreate the secret:
# Create a new .dockerconfigjson file
cat > /tmp/dockerconfig.json <<EOF
{
"auths": {
"registry.example.com": {
"username": "ci-bot",
"password": "NEW_PASSWORD"
}
}
}
EOF
# Encode and apply
kubectl create secret generic prometheus-registry-secret \
--from-file=.dockerconfigjson=/tmp/dockerconfig.json \
--type=kubernetes.io/dockerconfigjson \
-n monitoring --dry-run=client -o yaml | kubectl apply -f -
After applying, the secret contains the fresh credentials.
2. Pin the image tag explicitly
Update the deployment manifest to reference the exact version that exists in the registry:
# Before
image: registry.example.com/monitoring/prometheus
# After
image: registry.example.com/monitoring/prometheus:2.45.0
Apply the change:
kubectl apply -f prometheus-deployment.yaml
3. Adjust the egress policy
If using Istio, add an egress rule that permits traffic to the registry’s IP range (example CIDR 203.0.113.0/24).
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: registry-egress
namespace: monitoring
spec:
hosts:
- "registry.example.com"
ports:
- number: 443
name: https
protocol: TLS
resolution: DNS
location: MESH_EXTERNAL
endpoints:
- address: 203.0.113.45
Apply the ServiceEntry:
kubectl apply -f registry-egress.yaml
Verify
After the three corrective actions, confirm that the pod pulls the image successfully:
kubectl rollout restart deployment/prometheus -n monitoring
kubectl get pod -l app=prometheus -n monitoring -w
Expected pod status transition:
Pending → ContainerCreating → Running
Check the node logs for a successful pull:
journalctl -u kubelet | grep "Successfully pulled image"
Sample successful log entry:
time="2026-11-02T08:15:12Z" level=info msg="Successfully pulled image \"registry.example.com/monitoring/prometheus:2.45.0\""
Run a quick Prometheus query to ensure the service is operational:
curl -s http://prometheus.monitoring.svc.cluster.local:9090/api/v1/query?query=up | jq .
The response should contain a non‑zero value for the prometheus target.
Prevent
- Automate secret rotation: Use a controller (e.g.,
external-secretsorsealed-secrets) that watches the credential store and updates theimagePullSecretautomatically. - Pin image tags in manifests: Enforce a CI policy that rejects manifests lacking an explicit tag. Tag immutability reduces token‑scope mismatches.
- Version‑controlled egress policies: Store ServiceEntry or NetworkPolicy definitions in Git and review them during any registry credential change.
- Health‑check the registry: Deploy a sidecar that periodically performs a
curl -f https://registry.example.com/v2/and emits a Prometheus metric; alert on failures. - Document rotation procedures: Include steps to refresh secrets, update tags, and verify egress in the runbook.
FAQ
- Why does the pod show
ImagePullBackOffinstead of a clear “authentication failed” message?
Kubernetes normalizes registry errors into a generic back‑off state. The underlyingkubeletlogs contain the preciseunauthorizedmessage, which must be inspected. - Can I keep using the
latesttag if I rotate credentials?
No. The registry token is scoped to the specific image reference, including the tag. Usinglatestforces a new token request on each pull, which fails if the secret is stale. - Do I need to restart the kubelet after updating the secret?
No. Updating the secret and redeploying the workload forces the kubelet to fetch the new credentials on the next pull attempt. - How do I know if a network policy, not a secret, is the root cause?
Perform a manualcurlfrom a debug pod in the same namespace. A timeout or403indicates egress blockage, whereas a 401/403 after a successful TCP handshake points to authentication. - Is there a way to test the token generation before applying the secret?
Yes. Usedocker login registry.example.comwith the new credentials on a workstation; a successful login confirms that the token can be generated.
Related Topic Hub: Observability Troubleshooting Hub