What it means exactly, and why there are no logs
The Pod scheduled fine: it already has a node. The kubelet on that node started preparing the container and had to resolve everything the manifest references — environment variables from a ConfigMap, credentials from a Secret — before handing it to the runtime.
One of those references could not be resolved. The kubelet does not create a half-configured container: it aborts and leaves the container in `Waiting` with `reason: CreateContainerConfigError`, and the Pod stays `Pending`.
With no container there is no process, and with no process there is no output. `kubectl logs` will tell you the container has not started. It is the same misunderstanding as with ImagePullBackOff and it costs the same half hour: the answer is not in the logs, it is in the events.
The command that gives you the answer
`kubectl describe pod <name>` and scroll down to Events. The kubelet writes the literal reason there, and that text almost always names the exact object that is missing.
`configmap "my-config" not found` is a ConfigMap that does not exist under that name in that namespace. `secret "my-credentials" not found` is the same thing with a Secret.
`couldn't find key API_TOKEN in ConfigMap default/my-config` is different and more deceptive: the object does exist, but the key you ask for is not inside it. That happens when somebody renamed a key and updated some manifests but not others.
Read the whole message before theorising. Unlike other Kubernetes errors, this one is literal: it names the object and the namespace.
`CreateContainerConfigError` and `CreateContainerError` are not the same error
The names look alike and most guides treat them together, but they point at different layers and that changes where you look.
`CreateContainerConfigError` is about **configuration**: a ConfigMap, a Secret or a key is missing. The problem is in your manifests or in an object that was never created, and `kubectl apply` fixes it.
`CreateContainerError` is about **creation**: the configuration was complete, but the runtime could not create the container. A volume that will not mount, a container name already in use, a problem on the node itself. There you do not review the input YAML, you review the node.
Confusing them sends you to the wrong place: auditing manifests when the problem is the node, or draining a node when a Secret was missing.
The namespace is the cause that costs the most time
The Kubernetes documentation is explicit: the Pod and the ConfigMap **must be in the same namespace**. There is no way to reference a ConfigMap from another namespace out of a Pod `spec`.
That turns an apparently harmless operation into a guaranteed failure: copying a working Deployment from `staging` to `production` and applying only the Deployment. The manifest is right, the ConfigMap name is right, and the object exists — in the other namespace.
It costs the most time precisely because everything looks fine. `kubectl get configmap my-config` returns the object if you are in your usual namespace. The useful check carries the namespace explicitly: `kubectl get configmap my-config -n production`.
The same applies to Secrets, registry ones included. An `imagePullSecret` living in another namespace does not exist for this Pod.
Why the error shows up days after the change that caused it
This is the part that throws people, and it has a precise explanation: environment variables are injected **once, when the container is created**. The Kubernetes documentation states it the other way round but it is the same fact: ConfigMaps consumed as environment variables are not updated automatically and require a Pod restart.
The consequence is that deleting or renaming a ConfigMap **breaks nothing immediately**. Pods already running have their variables injected and keep working without noticing. The cluster looks healthy.
The error appears when something forces a new container: a node drain, a replica increase from the autoscaler, a restart from a failing probe, a node the provider recycles. That can be hours or days after the change, and by then nobody connects the two.
One detail makes it worse: ConfigMaps mounted as a **volume** do update themselves, with the kubelet sync cycle delay. So the same ConfigMap can be current in a container that mounts it and stale in one that reads it through environment variables. And if the mount uses `subPath`, it receives no updates either.
The silent variant: the Pod starts and the configuration never arrives
There is a case worse than the error, because it raises none. The Kubernetes documentation states it: environment variable names have a restricted character range, and if a ConfigMap key does not meet those rules, that key **is not made available to the container even though the Pod is allowed to start**.
With `envFrom` this is easy to trigger by accident. A ConfigMap meant to be mounted as files usually has keys like `application.properties` or `nginx.conf`. Consumed through `envFrom`, those keys are silently dropped: the Pod goes `Ready`, the application starts on defaults and fails later for a reason that does not look like a configuration problem.
The other silent path is deliberate: `optional: true` on the reference. It tells Kubernetes to tolerate the object being absent and start anyway. Useful for genuinely optional configuration, and a trap when somebody added it to «stop the deployment failing» — it turns a visible error into odd behaviour with no apparent cause.
The direct check: `kubectl exec <pod> -- env | sort` and confirm the variables you expected are actually there. A `Ready` Pod is not a configured Pod.
Apply order, which is where Helm and ArgoCD produce this on their own
A Deployment and its ConfigMap usually live in the same chart or the same directory, and nothing guarantees the ConfigMap lands first. If the Deployment is applied first, its Pods enter `CreateContainerConfigError` until the ConfigMap shows up.
Most of the time it resolves itself: the kubelet retries and the Pod starts once the object exists. That makes it easy to ignore, and it hides the case where it never resolves because the ConfigMap ended up in another sync phase or its template failed.
In Helm the order between resources in the same hook is not guaranteed beyond install order by kind; in ArgoCD you control it with sync waves. If the pattern repeats on every deployment, that is a sign the dependency should be declared rather than left to luck.
And a separate case: a static Pod `spec` cannot reference a ConfigMap or any other API object. If you are debugging a component that runs as a static Pod on the node, the reference is simply not valid.
How Moonin shortens that part
Diagnosing this error is short once you know which command to run. What costs time is the previous question: why that ConfigMap stopped being there, who changed it and when. And because the symptom can appear days after the cause, reconstructing it from `kubectl` does not work — the history is not there.
Moonin keeps a revision history per service and correlates cluster events with what changed before them. When a Pod enters `CreateContainerConfigError`, «what changed» already has an answer instead of starting an investigation.
It also surfaces the pattern that resolves itself and therefore nobody sees: Pods entering this state on every deployment and starting thirty seconds later. It is not an outage, so it fires no alerts, but it is an undeclared dependency waiting for the day the ConfigMap does not arrive.
See what changed in your own cluster
The demo is open and needs no form: you can look at how a service revision history appears with the configuration changes attached to it, which is what makes the «since when» question short.
See the revision history in the open demo · How Moonin is billed per Compute Unit