How to fix Kubernetes CreateContainerConfigError?
sre@prod-bastion ~ $ kubectl describe pod analytics-worker-0State: Waiting Reason: CreateContainerConfigError Message: secret "analytics-config" not foundEvents: Pulled in 127ms (893 MB) x71 over 22m restarts: 0# The image is on the node. Nothing ever ran, so there# are no logs. The failure is one step earlier, in the# config the kubelet could not assemble. sre@prod-bastion ~ $ kubectl get secret analytics-config
If a pod is stuck with CreateContainerConfigError, the image is fine. This is not an image pull problem and not an application crash. Kubernetes pulled the image, then tried to build the container's environment from the Secrets and ConfigMaps the spec references, and could not. The container never started, so there are no application logs to read. The failure happened one step earlier, during config assembly.
What does CreateContainerConfigError mean?
Before Kubernetes starts a container, it assembles everything the container needs to run: environment variables, mounted config, values pulled from Secrets and ConfigMaps. CreateContainerConfigError is the kubelet saying it could not complete that assembly. Some piece the pod spec asked for could not be resolved.
The important signal is the state. The container is in Waiting with reason CreateContainerConfigError, and the restart count is zero, because the process never ran. That combination, Waiting plus zero restarts, tells you the problem is upstream of the application entirely. Nothing in your code is involved yet.
It also tells you apart from the two states people confuse it with. ImagePullBackOff fails before the image is on the node. CrashLoopBackOff means the container did start and then died, which is why it has a rising restart count and previous logs to read, and it is worked a completely different way in our CrashLoopBackOff guide. CreateContainerConfigError sits between them: image present, process never launched, restart count pinned at zero. The pod lifecycle documentation sets out where each of the three sits.
How do you find what is missing?
Go straight to the pod's events, because Kubernetes almost always names the missing object for you.
kubectl describe pod <pod-name> -n <namespace> Scroll to the Events section at the bottom. You are looking for a message like secret "app-secrets" not found or couldn't find key DATABASE_URL in ConfigMap. That message is the answer most of the time. It tells you exactly which Secret or ConfigMap, and often which key, could not be resolved.
The same message comes straight out of the API, one line per container, which matters on a multi-container pod where a describe buries which container is actually stuck:
# Reason, message and restart count for every container in the podkubectl get pod <pod-name> -n <namespace> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\t"}{.state.waiting.reason}{"\t"}{.state.waiting.message}{"\trestarts="}{.restartCount}{"\n"}{end}' To see the whole blast radius rather than the one pod you were paged about:
# Every container blocked on config assembly, across all namespaceskubectl get pods -A -o json | jq -r ' .items[] as $p | $p.status.containerStatuses[]? | select(.state.waiting.reason == "CreateContainerConfigError") | "\($p.metadata.namespace)/\($p.metadata.name)\t\(.name)\t\(.state.waiting.message)"' One pod is a workload problem. Every pod in a namespace at once usually means a config bundle that never got applied, or got applied somewhere else.
When the message is not specific enough, list what the pod actually asks for, so you are checking references rather than guessing at them:
# Every Secret and ConfigMap this pod's containers referencekubectl get pod <pod-name> -n <namespace> -o json | jq -r ' .spec.containers[] | (.envFrom[]? | "envFrom \((.secretRef // .configMapRef).name)"), (.env[]? | select(.valueFrom) | .valueFrom | (.secretKeyRef // .configMapKeyRef) | select(.) | "valueFrom \(.name).\(.key)")' Then confirm each one exists in the pod's namespace and holds the key that was asked for:
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data}' | jq -r 'keys[]'kubectl get configmap <configmap-name> -n <namespace> -o jsonpath='{.data}' | jq -r 'keys[]' Listing the keys beats describe here, because it shows the exact strings, trailing spaces and case included, and a key that is nearly right is the whole failure.
What causes CreateContainerConfigError?
Three causes cover nearly every case, and they map to your spec's references.
A missing Secret. The pod references a Secret that does not exist in the namespace. Maybe it was never created, maybe it was created in a different namespace, maybe a deploy order left the pod starting before the Secret was applied. The event reads secret not found, and the container waits.
An absent key in a ConfigMap or Secret. The object exists, but the specific key the container asks for is not in it. The ConfigMap is there, but the DATABASE_URL key the spec references is missing or misspelled. The event names the key. This one is easy to miss because the object shows up in kubectl get, so a quick glance suggests everything is fine.
A namespace mismatch. The Secret or ConfigMap exists, but in a different namespace than the pod. Kubernetes resolves these references within the pod's own namespace only, so a Secret sitting one namespace over is invisible to the pod, and the reference is unresolvable. Everything looks correct if you list the object without checking which namespace you are in. One command settles it:
# Where does this object actually live?kubectl get secret -A --field-selector metadata.name=<secret-name> There is a fourth cause that is rarer and catches people out, because it has nothing to do with Secrets at all. If the pod's security context sets runAsNonRoot: true and the image's user resolves to root, or to a name rather than a numeric UID, the kubelet cannot build a valid container config and reports the same CreateContainerConfigError. The message says so directly: container has runAsNonRoot and image will run as root. If the events name a user rather than an object, stop looking for a missing Secret.
One state that is not this error, and is worth knowing so you do not chase it: a Secret or ConfigMap mounted as a volume that does not exist leaves the pod in ContainerCreating with a FailedMount event instead. Same missing object, different stage of startup, different symptom.
How does envFrom fail differently from valueFrom?
This distinction changes how loudly the failure shows up, and it explains why some config errors are obvious and others hide.
With valueFrom, the container pulls one specific key from a Secret or ConfigMap into one environment variable. If the object is missing, or the object exists but that exact key is not in it, config assembly fails with a message naming the key. The failure is precise, because you asked for one named thing and it was not there.
With envFrom, the container imports every key from a Secret or ConfigMap in bulk. Here the behavior differs in both directions. If the referenced object does not exist, you get CreateContainerConfigError naming the object, not a key, because there was no key to name. But if the object does exist, envFrom will not fail over its contents the way valueFrom does. Keys that are not valid environment variable names are skipped, and the kubelet records an InvalidVariableNames event rather than blocking the container. The pod starts, silently short a variable.
The practical takeaway: valueFrom failures point you at an exact key, envFrom failures point you at an entire object. Knowing which one your spec uses tells you whether to look for a missing key or a missing object, and whether a config problem would have stopped the container at all.
Both forms take an optional flag, and it is the difference between a pod that waits forever and one that starts:
env: - name: DATABASE_URL valueFrom: secretKeyRef: name: app-secrets key: DATABASE_URL optional: false # default: block startup if absent envFrom: - secretRef: name: feature-flags optional: true # start anyway if the whole Secret is absent Use optional: true only where the application genuinely has a default. Marking a required credential optional does not fix the config error, it moves the failure into the application, where it surfaces later and reads as an unrelated bug.
How do you fix CreateContainerConfigError?
Match the fix to what the event named.
If a Secret is missing, create it in the pod's namespace:
kubectl create secret generic app-secrets \ --from-literal=DATABASE_URL=postgres://... -n <namespace> If a ConfigMap key is absent, add the key to the existing ConfigMap, or fix the reference in the pod spec if the key name was simply wrong. A misspelled key in the spec is as common as a truly missing one.
kubectl patch configmap app-config -n <namespace> \ --type merge -p '{"data":{"LOG_LEVEL":"info"}}' If it is a namespace mismatch, create the object in the pod's own namespace, since references do not cross namespaces. The object existing somewhere is not enough, it has to exist here.
# Copy an existing Secret into the namespace that needs it, dropping the# resourceVersion, uid and creationTimestamp the API server will rejectkubectl get secret app-secrets -n <source-ns> -o json \ | jq 'del(.metadata.namespace, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp)' \ | kubectl apply -n <target-ns> -f - If the message named runAsNonRoot, set a numeric UID in the pod's security context, or use an image that already runs as a non-root user. On managed platforms the rules are identical, so an EKS or GKE cluster gives you nothing extra to check here.
After the fix, the pod recovers on its own once the kubelet retries, which it keeps doing on a short interval. To force a fresh attempt rather than wait:
kubectl rollout restart deployment/<name> -n <namespace> How do you prevent CreateContainerConfigError?
Get deploy order right. If your Secrets and ConfigMaps are applied by a separate process or a later step, a pod can start referencing them before they exist. Apply configuration before the workloads that depend on it, or use tooling that manages the ordering. Helm's --wait, Argo CD sync waves, and Kustomize's resource ordering all exist for exactly this.
Keep key names in sync between the spec and the objects. Most of these errors are a name that does not match: a key renamed in the ConfigMap but not in the spec, or a typo in either place. A rendered manifest is valid YAML whether or not the Secret it names exists, so nothing before the cluster catches this. What does catch it is a post-deploy gate:
# Fail the pipeline if anything is blocked on config assemblykubectl get pods -n <namespace> -o json | jq -e ' [.items[].status.containerStatuses[]? | select(.state.waiting.reason == "CreateContainerConfigError")] | length == 0' That turns a pod quietly waiting forever into a failed deploy, which is the difference between finding this in CI and finding it when the queue it was meant to drain backs up. Our Kubernetes examples page collects incidents that reached production exactly that way.
And confirm namespace alignment. A Secret and the pod that needs it must live in the same namespace, so make that part of your review when you move workloads between namespaces, which is exactly when the two drift apart.
What does a real CreateContainerConfigError look like?
Sherlocks AI investigated one on an analytics worker, written up in full as the CreateContainerConfigError investigation. The adhoc-worker container in the prod namespace was stuck for more than 22 minutes. The image was not the problem at all: it pulled cleanly in 127 milliseconds, 893 MB from a private ECR registry, repeatedly and with no auth errors. The failure was config assembly. The container used envFrom with a secretRef to a Secret named analytics-config, and that Secret did not exist in the namespace, so Kubernetes could never build the container's environment.
The numbers made the diagnosis clean. The kubelet had retried 71 times over 22 minutes with a restart count of zero. Zero restarts over that long means the container never ran even once, which rules out the application entirely before you read a line of it. A fast, clean image pull followed by a long wait in Waiting state is the signature of a config problem, not an image problem, and the fix lived entirely in the missing Secret rather than in the workload.
That is the pattern in miniature, and it is worth reading the way the checks fell: the registry and the image were cleared first, which is what left config assembly as the only place the failure could be.
Kubernetes CreateContainerConfigError FAQ
Is CreateContainerConfigError an image problem? No. The image pulled fine. Kubernetes could not assemble the container's config from its Secrets or ConfigMaps.
How do I find what is missing? Run kubectl describe pod and read the Events. It usually names the exact Secret, ConfigMap, or key that could not be resolved.
Why does the object exist but the pod still fails? Either the specific key it references is absent, or the object is in a different namespace than the pod.
Do Secret and ConfigMap references cross namespaces? No. A pod can only reference Secrets and ConfigMaps in its own namespace, so a mismatch leaves the reference unresolvable.
What is the difference between envFrom and valueFrom failures? valueFrom fails on a missing named key. envFrom fails on a missing whole object, since it imports every key in bulk.
Why are there no application logs? The container never started, so there is nothing to log. The failure happened before the process ran.
How is this different from CrashLoopBackOff? CrashLoopBackOff means the container ran and died, so it has restarts and previous logs. Here the restart count stays at zero.