Quick answer: the kubelet can't find a Secret your pod references. Create the Secret in the same namespace with the exact name and keys, and the pod will recover on its own.
This error shows up as CreateContainerConfigError in kubectl get pods, and the status flips to CrashLoopBackOff because the kubelet keeps retrying and keeps failing. It's not a crash in your app — the container never even starts. The kubelet tries to build the container config (env vars, volume mounts, image pull secrets) before handing it to the runtime, and one of those references points to a Secret that doesn't exist in that namespace. Common triggers: someone copy-pasted a deployment manifest from staging into prod and forgot the Secret, a Helm chart references a Secret that's only created when a feature flag is on, or a CI pipeline deletes and recreates the Secret and the apply order is wrong. I've seen this a dozen times on EKS and GKE where the Secret lives in a different namespace than the workload.
Step 1 — Confirm it's really a missing Secret
Don't guess. Read the events.
kubectl describe pod <pod-name> -n <namespace>
You'll see something like:
Error: secret "my-app-secret" not found
That message names the missing Secret. Note it exactly — case-sensitive, including dashes. Kubernetes names are case-sensitive and people fat-finger this constantly.
Step 2 — Check if the Secret exists at all
kubectl get secret -n <namespace>
kubectl get secret <secret-name> -n <namespace> -o yaml
If it returns nothing, the Secret is gone. If it exists but the pod still fails, jump to Step 4 — you've got a key-level mismatch or a namespace mismatch, not a missing Secret.
Step 3 — Create the Secret in the right namespace
The secret must live in the same namespace as the pod. No cross-namespace references exist in vanilla Kubernetes. From a literal:
kubectl create secret generic my-app-secret \
--from-literal=DB_PASSWORD='correct-horse-battery-staple' \
--from-literal=API_KEY='abc123' \
-n <namespace>
Or from a file:
kubectl create secret generic my-app-secret \
--from-file=tls.crt --from-file=tls.key \
-n <namespace>
Once it exists, the kubelet picks it up on the next retry — usually within 30–60 seconds. You don't need to delete the pod. Watch it with:
kubectl get pod <pod-name> -n <namespace> -w
Step 4 — Key mismatch, not name mismatch
If the Secret exists but you still get CreateContainerConfigError, the pod is asking for a key that isn't in the Secret. Compare the deployment's envFrom/valueFrom against the actual keys:
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data}'
That prints the base64-encoded key list. If your manifest says secretKeyRef.key: db-password but the Secret has DB_PASSWORD, you get the same generic error. Keys are case-sensitive too. Fix the manifest, or add the missing key. Same deal for volume mounts pointing at a key that doesn't exist.
Alternative fixes
- Namespace mismatch. Run
kubectl get pods -A | grep my-appand compare the namespace column to where the Secret actually lives. Moving the Secret to the right namespace is the fix — don't try to make cross-namespace refs work, they don't. - Helm race condition. If it's a Helm chart, the Secret may be templated with a conditional (e.g.
{{- if .Values.existingSecret }}). Either supplyexistingSecretor let the chart create it.helm templatelocally and read the output before you blame Kubernetes. - External Secrets Operator lag. If ESO or Vault injects the Secret, the pod can start before the Secret exists. Add a dependency or use an init container that waits for the Secret. Don't just crank the backoff — solve the ordering.
- Image pull secret missing. Same error class. If your private-registry pull secret was deleted, the kubelet can't build the config. Recreate the
docker-registrysecret in the namespace.
Prevention
Secrets are the most common cause of this error because they're external state the manifest silently depends on. Two things help more than anything else: run a pre-deploy check that verifies every secretKeyRef resolves before you apply the workload, and put Secrets in the same Helm chart or Kustomize overlay as the deployment that consumes them so they move together. Also, turn off automountServiceAccountToken unless you need it — one less secret reference to break.
If you're on a recent version of Kubernetes (1.27+), enable the SecretManager style tooling with kubectl events to catch these earlier. But honestly, a five-line script that greps your manifests for secretKeyRef and checks kubectl get secret will save you more pain than any operator.
One more thing: don't delete the pod to "reset" the loop. It won't help — the kubelet retries anyway, and you lose the event history that would've told you which Secret was missing.