CreateContainerConfigError means the kubelet could not assemble the container's configuration. It never tried to start your image, so nothing in your application is responsible.
Something the Pod spec references does not exist, and the cause is almost always one of four things: a missing Secret, a missing ConfigMap, a key that is absent from one that does exist, or a missing service account.
One command gives you the answer
kubectl describe pod payments-api-7d4f-x8k2 | tail -10
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulled 2m kubelet Container image already present
Warning Failed 2m (x8 over 2m) kubelet Error: secret "payments-db-credentials" not found
The Error: line names the exact object. That is the whole diagnosis. There is rarely any need to look further.
kubectl get events --field-selector involvedObject.name=payments-api-7d4f-x8k2 \
--sort-by=.lastTimestamp
Cause 1: The Secret or ConfigMap does not exist
Error: secret "payments-db-credentials" not found
kubectl get secret payments-db-credentials
Error from server (NotFound): secrets "payments-db-credentials" not found
Three reasons it is absent, in order of frequency.
Wrong namespace. Secrets and ConfigMaps are namespaced, and there is no cross-namespace reference. A Secret in default is invisible to a Pod in payments:
kubectl get secret payments-db-credentials -A
NAMESPACE NAME TYPE DATA AGE
default payments-db-credentials Opaque 2 6d
There it is, in the wrong namespace. Copy or recreate it where the Pod runs.
Ordering. The Deployment was applied before the Secret. Kubernetes does not queue and retry indefinitely in a useful way here; it just keeps failing. Apply the Secret and the Pod recovers on its next attempt with no restart needed.
A typo. Compare exactly, since payments-db-credential and payments-db-credentials look identical at a glance:
kubectl get pod payments-api-7d4f-x8k2 \
-o jsonpath='{range .spec.containers[*].envFrom[*]}{.secretRef.name}{"\n"}{end}{range .spec.volumes[*]}{.secret.secretName}{"\n"}{end}'
Cause 2: The key is missing from an object that exists
Error: couldn't find key DATABASE_URL in ConfigMap payments/app-config
This is the subtler version, and the one that survives a "the ConfigMap is there" check.
kubectl get configmap app-config -o jsonpath='{.data}' | python3 -m json.tool
{
"LOG_LEVEL": "info",
"database_url": "postgres://..."
}
DATABASE_URL versus database_url. Keys are case-sensitive, and this is the most common form of this error. Someone renamed a key, or the manifest and the ConfigMap were written by different people.
kubectl get secret payments-db-credentials -o jsonpath='{.data}' | python3 -c "import json,sys; print(list(json.load(sys.stdin).keys()))"
['password', 'username']
That lists a Secret's keys without printing the values, which is what you usually want.
Make a key optional when it genuinely is
env:
- name: FEATURE_FLAG_URL
valueFrom:
configMapKeyRef:
name: app-config
key: FEATURE_FLAG_URL
optional: true
With optional: true the variable is simply unset rather than blocking the Pod. Use it for genuinely optional configuration, and not as a way to silence this error on something the application needs, which converts a clear startup failure into a confusing runtime one.
The same flag works on secretKeyRef, and on whole envFrom entries.
Cause 3: Missing service account
Error: serviceaccount "payments-api" not found
kubectl get serviceaccount payments-api
A Deployment referencing a service account that was never created, or that lives in another namespace. Common after copying manifests between environments where the service account is created by a separate chart or Terraform module.
Cause 4: A malformed key name
Environment variable names have rules, and a key that is fine in a ConfigMap may be invalid as a variable:
Error: failed to create subPath directory for volumeMount
Error: invalid environment variable name "app.database.url"
A ConfigMap key containing dots is valid as a key and invalid as an environment variable name. Mount it as a file instead, or rename the key.
CreateContainerConfigError or CreateContainerError
Two similar statuses with different meanings, and distinguishing them saves time:
| Status | Meaning |
|---|---|
CreateContainerConfigError | Configuration could not be assembled: missing Secret, ConfigMap, key or service account |
CreateContainerError | Configuration is fine; creating the container failed: bad command, mount problem, runtime error |
CrashLoopBackOff | The container started and then exited repeatedly |
CreateContainerConfigError never runs your image. CrashLoopBackOff means it ran and failed, which is a different investigation entirely.
Finding every Pod with the problem
kubectl get pods -A --field-selector=status.phase=Pending \
-o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,STATUS:.status.containerStatuses[0].state.waiting.reason \
| grep -i configerror
Several Pods failing on the same object usually means one missing Secret across a namespace: a deleted sealed secret, an External Secrets sync failure, or a Terraform apply that did not complete.
Preventing it
Apply Secrets and ConfigMaps before workloads, or use Helm, which orders them by default with hooks available for anything that needs it.
Use optional: true for configuration that genuinely is.
Validate references in CI. A simple check comparing every configMapKeyRef and secretKeyRef in your manifests against the keys that exist catches this before a deploy rather than during one.
Prefer envFrom with whole objects where you consume most keys, since it removes the per-key name coupling that causes cause 2.
A checklist
kubectl describe pod <name> | tail -10. TheError:line names the object.- "not found" → does it exist?
kubectl get secret <name> -Ato check every namespace. - Found elsewhere → namespace mismatch; there is no cross-namespace reference.
- "couldn't find key" → list the actual keys and compare case exactly.
- Genuinely optional → add
optional: true. - "serviceaccount not found" → create it in the Pod's namespace.
- Many Pods affected → one missing object, likely a failed secret sync.
- Fix the object and the Pod recovers on its own; no restart needed.
Frequently Asked Questions
What causes CreateContainerConfigError in Kubernetes?
The kubelet could not assemble the container's configuration, so it never started your image. Four causes account for nearly all of it: a Secret that does not exist, a ConfigMap that does not exist, a key missing from one that does, or a missing service account. kubectl describe pod puts the exact object name in the events Error: line, which is usually the complete diagnosis, no application logs are involved, because the application never ran.
Why does Kubernetes say my Secret is not found when it exists?
Almost always a namespace mismatch. Secrets and ConfigMaps are namespaced and cannot be referenced across namespaces, so a Secret in default is invisible to a Pod in payments. Run kubectl get secret <name> -A to see every namespace at once. Finding it somewhere else is the answer. The other causes are a typo in the reference and the Deployment having been applied before the Secret existed.
What does "couldn't find key X in ConfigMap" mean?
The ConfigMap exists but does not contain that key, and keys are case-sensitive, DATABASE_URL and database_url are different. List what is actually there with kubectl get configmap <name> -o jsonpath='{.data}' and compare exactly. This is the version of the error that survives a quick "the ConfigMap is there" check, and it usually follows a rename or a manifest written against a different environment.
How do I make a ConfigMap or Secret key optional?
Add optional: true alongside configMapKeyRef or secretKeyRef, and the environment variable is simply left unset rather than blocking the Pod. It works on whole envFrom entries too. Use it for configuration that genuinely is optional. Applying it to something the application needs converts an obvious startup failure into a confusing runtime one, which is a considerably worse outcome.
What is the difference between CreateContainerConfigError and CrashLoopBackOff?
CreateContainerConfigError means the container was never created, because its configuration could not be assembled from the referenced Secrets, ConfigMaps and service account. CrashLoopBackOff means the container was created, started, and exited repeatedly, so your image ran and something inside it failed. The first is a manifest or cluster-object problem with no application logs to read; the second needs kubectl logs --previous.
Do I need to restart the Pod after creating the missing Secret?
No. The kubelet keeps retrying container creation with backoff, so creating the missing object lets the Pod proceed on its own within a minute or two. You only need to intervene if the Pod spec itself is wrong, a typo in the reference, or a key name that does not exist, since that requires editing the Deployment, which triggers a new ReplicaSet and new Pods anyway.