koti.dev
← The Runbook
Mastering Kubernetes the Right Way · DAY 17 / 35

Kubernetes volumeMount References Undefined Volume: The Typo Fix

API rejects the pod, the error scrolls past, and you lose an hour to one missing block.

KV
Koti Vellanki05 Apr 20263 min read
kubernetesdebuggingstorage
Kubernetes volumeMount References Undefined Volume: The Typo Fix

1:52 AM and a junior engineer was on a call with me, voice cracking. "The apply worked but the pod never starts." He had shipped a Helm change at midnight, gone home, and now nothing in the namespace would come up. He had read the error message four times. It said "volume not found" and that made him look at PVCs, at StorageClasses, at the CSI driver. He spent forty minutes on the wrong thing. The fix was one line in the values file. Somebody had renamed the volume in the volumes block but the volumeMounts reference still pointed at the old name. The API server rejected every pod at creation time and the error had scrolled off his terminal ninety seconds after he ran the apply.

The scenario

DAY 17 · STORAGE · VOLUME MOUNT

The pod is stuck in ContainerCreating. The Secret does not exist.

The pod references secret/app-config in its volumes block. The Secret does not exist in the default namespace. The kubelet volume manager retries the mount on a back-off loop. No container starts until every volume mounts successfully. The FailedMount event is the first place to look.

FIGURE17 / 35
Pod stuck in ContainerCreating — secret/app-config not found in namespaceA pod references secret/app-config in its volumes block. The Secret does not exist in the default namespace. The kubelet volume manager retries the mount with exponential back-off, emitting a FailedMount event each time. The pod stays in ContainerCreating indefinitely because no container starts until every volume mounts.KUBERNETES CLUSTERcluster · v1.30POD · default nsvolumes:- secret:secretName: app-configContainerCreatingwaiting: FailedMount1MountVolumeretry loopKUBELET volume managermount loopmount request:requested:secret/app-configattempt:8next retry:20sno container started2MountVolume→ secret not foundNAMESPACE · defaultsecret lookupsecrets present:tls-certdb-passwordapp-configNOT FOUNDkubectl get secret -n defaultapp-config not listed3
1

The pod never starts — it is stuck waiting on a volume

The kubelet will not exec the container entrypoint until every volume in the pod spec mounts successfully. A missing Secret is one of the most common causes of ContainerCreating that never resolves. Run kubectl describe pod and look for a FailedMount event naming the Secret.

2

The kubelet retries on a back-off loop

The volume manager polls with exponential back-off. After several attempts the interval grows to tens of seconds. The pod sits in ContainerCreating indefinitely — it does not transition to Error or CrashLoopBackOff, because no process has ever run. The first event is the signal, not the status.

3

Create the Secret in the correct namespace

Run kubectl get secret -n default to confirm the Secret is absent. Create it: kubectl create secret generic app-config --from-file=config.yaml. The kubelet picks it up on the next reconcile cycle — no pod restart needed. Secrets are namespace-scoped; a Secret in a different namespace is invisible to the pod.

Kubernetes
Volume manager
Missing resource
Mount path
◆ koti.dev / runbook
A pod stuck in ContainerCreating — kubelet cannot mount secret/app-config because no such Secret exists in the namespace.
A Kubernetes pod in ContainerCreating state references a Secret named app-config in its volumes block. The kubelet volume manager repeatedly tries to mount the Secret, incrementing the attempt counter each time. A lookup in the default namespace shows tls-cert and db-password exist but app-config is not found. No container starts until the volume mount succeeds.
pod.spec.volumes.secret.secretName — kubectl explain pod.spec.volumes.secret · Pod status condition reason "FailedMount" — emitted by kubelet volume manager · kind v0.22.0, Kubernetes 1.30.0

The repo ships a clean reproduction. One pod, one volumeMount, zero volumes defined. Watch the API handle it.

bash
git clone https://github.com/vellankikoti/troubleshoot-kubernetes-like-a-pro.git cd troubleshoot-kubernetes-like-a-pro/scenarios/volume-mount-issue ls
bash

Open issue.yaml and you will see a volumeMounts block referencing missing-volume, and no volumes block at all. That is the shape of a thousand broken deploys.

Reproduce the issue

bash
kubectl apply -f issue.yaml
bash
plaintext
The Pod "volume-mount-issue-pod" is invalid: spec.containers[0].volumeMounts[0].name: Not found: "missing-volume"

If you are lucky, you catch that error at apply time. If you are unlucky, it got buried by three hundred lines of Helm output, the Pod never got created, and you are hunting a ghost.

bash
kubectl get pod volume-mount-issue-pod # Error from server (NotFound): pods "volume-mount-issue-pod" not found
bash

No pod. No event. No describe. Because the API rejected the object before it ever became a real pod in etcd.

Debug the hard way

bash
kubectl apply -f issue.yaml --dry-run=server # The Pod "volume-mount-issue-pod" is invalid: spec.containers[0].volumeMounts[0].name: Not found: "missing-volume"
bash

That dry-run is the trick. Server-side validation replays the exact error without touching the cluster.

bash
kubectl explain pod.spec.containers.volumeMounts.name
bash
plaintext
FIELD: name <string> DESCRIPTION: This must match the Name of a Volume.

The schema is literally telling you what to check. If the name does not match a volume in spec.volumes, the pod is invalid.

bash
grep -n "name:" issue.yaml
bash
plaintext
4: name: volume-mount-issue-pod 7: - name: busybox 10: name: missing-volume

Three names. Only one of them belongs in the mount. The volumes block is missing entirely.

Why this happens

Pod specs have two parallel sections that have to stay in sync: spec.volumes defines the volumes available to the pod, and spec.containers[].volumeMounts declares where each container wants to attach them. The API server validates that every volumeMounts.name has a matching entry in spec.volumes. If it does not, the pod is rejected before it even reaches the scheduler. No events, no describe output, nothing for kubectl to show you after the fact.

This breaks most often during refactors. Somebody renames a volume in the volumes block, runs tests against one container, and misses the second container that still uses the old name. Or a Helm template generates the mount name from one variable and the volume name from another, and they drift. The failure mode is identical: the first time the generated YAML hits the API server, it gets an HTTP 422 and disappears.

The trap is that the error is clear when you see it and invisible when you miss it. There is no graceful fallback, no warning event, no half-created pod. It is binary: accepted or rejected.

The fix

bash
kubectl apply -f fix.yaml
bash

The diff from issue to fix adds the missing volumes block and renames the mount to match:

yaml
volumeMounts: - mountPath: "/mnt/data" name: correct-volume volumes: - name: correct-volume emptyDir: {}
yaml
bash
kubectl get pod volume-mount-fixed-pod # volume-mount-fixed-pod 1/1 Running 0 12s
bash

The lesson

  1. API rejection errors happen at apply time and disappear from your scrollback in seconds. Always check the exit status and the last line of output.
  2. kubectl apply --dry-run=server is the fastest way to replay a validation error without touching the cluster.
  3. Volume names in volumeMounts and volumes are a contract. The API server is the enforcement, and it is unforgiving.

Day 17 of 35. Tomorrow, the PVC that refuses to bind, and the five distinct reasons it might be sulking.

◆ Newsletter

Get the next post in your inbox.

Real Kubernetes lessons from seven years in production. One email when a new post drops. No spam. Unsubscribe in one click.