skip to content

A pod sits in the status CreateContainerConfigError and never produces any application logs. What class of problem does that status indicate, and how is it different from a container that starts and then crashes?

level: middleimportance: should knowfreq 44%

answer

  1. Fails before the container exists → no logs, no exit code
  2. Message names the object: 'configmap X not found' / 'couldn't find key K'
  3. ConfigMaps and Secrets are namespace-scoped
  4. Self-heals when the missing object is created — no pod delete needed
  5. optional: true skips a missing source deliberately

basics

~20 s

The kubelet cannot assemble the container's configuration, almost always because a referenced ConfigMap, Secret or a specific key inside one does not exist. The container is never created, so there is no exit code and no logs — the detail is in the pod's Events.

solid answer

~50 s

`CreateContainerConfigError` happens after the image is pulled but before the container is created. The kubelet resolves everything the container spec references — env vars from `valueFrom`/`envFrom`, volumes projecting ConfigMaps and Secrets — and if any of it is missing, it stops there and reports this status. `kubectl describe pod` names the object exactly: `configmap "app-config" not found` or `couldn't find key DATABASE_URL in Secret default/app-secrets`. The key difference from CrashLoopBackOff is *when* it fails. CrashLoopBackOff means the container ran and its process exited, so there is an exit code and usually logs. CreateContainerConfigError means nothing ever ran: no exit code, `kubectl logs` returns an error, and `--previous` is useless. It is self-healing in the sense that the kubelet keeps retrying — create the missing ConfigMap or Secret and the pod proceeds without being recreated. If a reference is genuinely optional, mark it `optional: true` so a missing source is skipped instead of blocking startup.

code

bash · 5 lines
bash
kubectl get pod api-0 -n team-a \
  -o jsonpath='{.status.containerStatuses[0].state.waiting.reason}{": "}{.status.containerStatuses[0].state.waiting.message}{"\n"}'
# CreateContainerConfigError: couldn't find key DATABASE_URL in Secret team-a/app-secrets

kubectl get secret app-secrets -n team-a -o jsonpath='{.data}' | tr ',' '\n'

go deeper

for a junior

Recognise that the status means a referenced ConfigMap or Secret is missing, and know to read the message in kubectl describe rather than reaching for logs.

for a middle

Place it correctly in the startup pipeline, explain namespace scoping and missing-key versus missing-object messages, and use optional: true appropriately.

for a senior

Treat it as an ordering or sync signal in the delivery pipeline, and be able to distinguish it from FailedMount, CreateContainerError and RunContainerError by the evidence each leaves.

for a principal

Address it as a platform property: apply ordering or sync waves, secret-sync operators and their failure modes, and admission-time validation so missing configuration is caught before rollout rather than at container start.

## Where it sits in the container startup sequence Starting a container is a pipeline, and each stage has its own failure status: 1. **Schedule the pod** onto a node — failure: pod stays `Pending` with `FailedScheduling`. 2. **Mount volumes** — failure: `FailedMount`, pod stuck in `ContainerCreating`. 3. **Pull the image** — failure: `ErrImagePull` → `ImagePullBackOff`. 4. **Build the container configuration** (env, mounts, security context) — failure: **`CreateContainerConfigError`**. 5. **Create the container** in the runtime — failure: `CreateContainerError`. 6. **Start the process** — failure: `RunContainerError`. 7. **Process runs, then exits** — failure: `CrashLoopBackOff` with an exit code. Knowing the position tells you immediately what evidence exists. Anything at stage 4 or earlier means no process ever ran, so `kubectl logs` cannot help and there is no exit code to interpret. All the information is in `kubectl describe pod`'s Events and container status message. ## What actually triggers it By far the most common causes are unresolved configuration references: - **Missing ConfigMap or Secret.** `envFrom: [{configMapRef: {name: app-config}}]` where `app-config` does not exist in the pod's namespace. Message: `configmap "app-config" not found`. - **Missing key.** The object exists but the specific key does not: `env: [{valueFrom: {secretKeyRef: {name: app-secrets, key: DATABASE_URL}}}]`. Message: `couldn't find key DATABASE_URL in Secret default/app-secrets`. - **Wrong namespace.** ConfigMaps and Secrets are namespace-scoped and cannot be referenced across namespaces. A manifest copied between namespaces fails here. - **Invalid content for the field.** For example a `securityContext.runAsUser` that conflicts with the image's `USER` under a `runAsNonRoot: true` policy surfaces at container creation rather than as a crash. A related but distinct failure is a ConfigMap or Secret used as a *volume* that does not exist: that typically hangs at `ContainerCreating` with a `FailedMount` event instead, because the failure happens during volume setup rather than config assembly. ## Why it is so often a race, not a bug The classic scenario is applying a Deployment and its ConfigMap in the same change, with the Deployment landing first — or a Secret that a CI pipeline or an external-secrets operator has not synced yet. The pods come up, cannot resolve the reference, and sit in `CreateContainerConfigError`. Nothing is permanently broken: the kubelet retries with backoff, and the moment the missing object appears the container is configured and started. No pod deletion is required. This is also why the status is a useful signal in GitOps setups: it tells you an ordering or sync dependency exists that your delivery tooling is not honouring. Server-side apply of a whole manifest set, or a sync-wave/dependency ordering in the delivery tool, removes the window. ## Optional references Both `configMapKeyRef`/`secretKeyRef` and `envFrom` support `optional: true`. With it, a missing source or key is silently skipped and the container starts with the variable unset. That is right for genuinely optional feature flags and wrong for anything the application needs to function — an app that starts with a missing `DATABASE_URL` and then crashes at first query is harder to diagnose than one that never starts with a message naming the exact key. Use `optional` deliberately, not to make an error message go away. ## Diagnosing it ``` kubectl describe pod <pod> | sed -n '/Events/,$p' kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].state.waiting.message}' ``` The message names the object and, when relevant, the key. Then verify existence and content in the *pod's* namespace: ``` kubectl get configmap app-config -n team-a kubectl get secret app-secrets -n team-a -o jsonpath='{.data}' | tr ',' '\n' ``` Note that key names are case-sensitive and that Secret keys must be valid environment variable names when consumed via `envFrom` — invalid names are skipped (with an event) rather than injected. ## The wider habit When triaging any stuck pod, first classify it by which stage of the pipeline it died at, because that determines which tool gives you evidence. `Pending` → scheduling; `ContainerCreating` → volumes/network; `ImagePullBackOff` → registry; `CreateContainerConfigError` → missing config objects; `CrashLoopBackOff` → the application itself, and only then are logs and exit codes meaningful.

  • You create the missing ConfigMap while the pod is in CreateContainerConfigError. Do you need to delete the pod?
    No. The kubelet keeps retrying container creation with backoff, so once the ConfigMap exists in the pod's namespace the next attempt resolves the reference and the container starts. Deleting the pod only shortens the wait for a backed-off retry. Note the contrast with an already-running container: values injected as environment variables are fixed at container start, so a later ConfigMap change requires a restart to take effect, whereas a ConfigMap mounted as a volume is updated in place.
  • How does CreateContainerConfigError differ from a pod stuck in ContainerCreating with a FailedMount event?
    They fail at different stages. FailedMount happens during volume setup — a PersistentVolumeClaim that is not bound, a CSI attach that has not completed, or a ConfigMap/Secret volume that does not exist — and leaves the pod in ContainerCreating. CreateContainerConfigError happens later, when the kubelet assembles the container's configuration and cannot resolve an env-var reference. Both mean no process ran, but the first points at storage and the second at configuration objects.

saying these in an interview costs you the question

  • Running kubectl logs --previous and concluding the app is silent, when no container ever existed
  • Assuming a ConfigMap in another namespace can be referenced from this pod
  • Adding optional: true to required configuration just to make the pod start
  • Deleting and recreating the pod repeatedly instead of creating the missing object
  • Confusing it with CrashLoopBackOff and hunting for an application bug

context