Home › DevOps › Kubernetes Unbound PVC: Fix Pending Pods
Intermediate 6 min · September 23, 2026

Kubernetes Unbound PVC: Fix Pending Pods

Describe the PVC to read its Events, align storageClass, size, and accessModes, then fix provisioning.

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
✓ Production
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
Before you start⏱ 14 min
  • ✓A Kubernetes cluster you can inspect (minikube, kind, or a dev namespace)
  • ✓kubectl installed and pointing at that cluster
  • ✓Basic comfort reading a Deployment manifest and pod events
 ● Production Incident 🔎 Debug Guide
⚡Quick Answer
  • Unbound means the claim found no volume: the pod stays Pending because the scheduler won't place a pod whose storage doesn't exist yet
  • Read the reason directly: kubectl describe pvc prints Events like storageclass not found, no matching volumes, or provisioner errors
  • Match all three binding inputs: storageClassName must exist, requested size must fit a volume, and accessModes (RWO/RWX) must be supported
  • If binding should be automatic, check the StorageClass provisioner and its controller logs — typos and missing drivers block dynamic provisioning
✦ Definition~90s read
What is Kubernetes Unbound PVC Fix?

Persistent storage in Kubernetes is a two-sided handshake. The developer writes a PersistentVolumeClaim: I need 10Gi, ReadWriteOnce, from StorageClass fast-ssd. The cluster supplies a PersistentVolume meeting those terms — either pre-created by an admin (static provisioning) or carved on demand by a provisioner (dynamic provisioning, the norm in cloud).

★
Imagine a hotel request slip: non-smoking double, third floor.

Binding glues one claim to one volume; only bound claims let their pods schedule. The scheduler explicitly refuses Pending pods with unbound claims because starting a container without its disk would corrupt data or crash on first write.

VolumeBindingMode controls timing. Immediate binds the claim at creation, before any pod exists — simple, but it can provision in the wrong zone, stranding the pod later. WaitForFirstConsumer delays binding until a pod needs the claim, letting the scheduler pick a zone first — smarter topology, but the claim looks unbound (and the pod waits longer) while scheduling decides.

Neither mode is broken; misreading the mode as failure is the common confusion. kubectl get pvc shows the phase (Pending vs Bound) and kubectl describe pvc Events explain why.

What it is NOT: it isn't a broken container image (images never entered the picture), it isn't a resource quota on CPU or memory (those produce FailedScheduling with different reasons), and it isn't fixed by deleting and recreating the pod (the claim is still unbound, so the replacement waits identically). Don't restart workloads or bump replicas — read the claim's Events, fix the storage inputs it names, and watch binding complete on its own.

Plain-English First

Imagine a hotel request slip: non-smoking double, third floor. The desk matches your slip to a free room — or leaves you waiting in the lobby if none fits. A PVC is your slip, a PersistentVolume is the room, and the provisioner is the clerk. Unbound means you're still in the lobby: no room matches, the type was misspelled, or the clerk never showed. Your pod waits with you — Pending — until the paperwork resolves.

Your Deployment is applied, the pods exist, and every one sits in Pending with "0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims." No container started, no logs exist, and kubectl logs returns nothing because there's nothing to log. Storage failures are uniquely frustrating: the workload is correct, the images are fine, and the cluster refuses to schedule over paperwork — a claim with no matching volume.

The error compresses several distinct causes into one sentence. The StorageClass may not exist (a typo, or a class from another cloud's tutorial). The claim may ask for more than any volume offers, or an access mode the driver can't provide. Binding may be delayed by design (WaitForFirstConsumer) while you expected instant. Or dynamic provisioning is broken — the provisioner pod is missing, misconfigured, or erroring — so no volume ever gets created.

This guide works the chain in order: read the PVC events, verify class and static volumes, reconcile size and access modes, understand the binding mode, and debug the provisioner. You'll get the kubectl commands that reveal each link and manifests that bind correctly.

Start at the Claim: Events Name the Cause

kubectl describe pod tells you the pod is waiting; kubectl describe pvc tells you why. The claim's Events section is the single richest diagnostic in storage debugging: the controller writes exactly what blocked binding — a StorageClass it can't find, a provisioner call that failed with the cloud error attached, or a static match that found no candidates. Teams that start at the pod burn hours on node and image theories; teams that start at the claim read the answer in thirty seconds. Make describe pvc your reflex for every Pending pod with a volume.

Pair it with the wide view: kubectl get pvc shows phase (Pending vs Bound), age (how long it's waited), and volume (empty until bound). A claim Pending for 3 hours with FailedProvisioning events repeating is a provisioner problem; Pending with no events at all suggests the class doesn't exist (no controller watches it) or the binding mode is waiting on scheduling. kubectl get events --sort-by=.lastTimestamp -n <ns> | grep -i -E 'pvc|provision|volume' gives the namespace timeline when multiple claims fail together.

Practice reading the three canonical messages until they're instant pattern matches. storageclass.storage.k8s.io X not found means fix the name. no persistent volumes available means static binding found nothing (check the PV table). ProvisioningFailed with a cloud error means the driver called the API and lost — read the attached message, not the prefix. Each message maps to exactly one section below.

💡Pod Events Say Waiting, PVC Events Say Why
The pod only knows its claim is unbound. The claim knows the missing class, the failed provisioner call, or the empty candidate set. Describe the PVC first — it's the difference between a 30-second diagnosis and a 3-hour driver reinstall.
📊 Production Insight
The Wednesday incident team read pod events for 3 hours while the PVC events named the missing class the whole time. One describe command separated the symptom from the cause.
🎯 Key Takeaway
describe pvc first, get pvc for phase and age, namespace events for the timeline. Memorize the three canonical messages.

StorageClass Names: Spelling Is the API

The storageClassName field is a strict string match against a cluster-scoped object — one wrong character and no provisioner engages, with no fuzzy matching and no helpful suggestion. Cross-environment copies are the top source: fast-ssd-prod in a cluster that defines fast-ssd, gp2 from an AWS tutorial applied to GKE, or a Helm values file overriding the class per environment with a stale entry. kubectl get sc lists the actual vocabulary; the claim must use exactly one of those words or nothing happens.

Mind the empty-string edge. A claim with no storageClassName requests the cluster's default class (the one annotated storageclass.kubernetes.io/is-default-class). If no default exists, an empty class means static-only binding — dynamic provisioning silently never starts. Conversely, an explicit class on the claim and a different (or empty) class on a static PV never bind: both sides must name the same class or both omit it. Check both objects, not just the claim.

Fixes are small and permanent. Correct the name in the manifest or values file, set a default class per cluster so bare claims provision, and standardize class names across environments so overlays can't diverge. The pipeline gate from the incident — every rendered storageClassName must exist in the target cluster — turns this entire section into a CI failure instead of a 3 AM page. Spelling is the API; lint it like one.

storageclass-name-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
# The cluster's actual vocabulary
kubectl get sc -o custom-columns=NAME:.metadata.name,PROVISIONER:.provisioner,MODE:.volumeBindingMode,DEFAULT:.metadata.annotations

# What the claim asks for (class, size, modes in one line)
kubectl get pvc data-postgres-0 -n staging -o jsonpath='class={.spec.storageClassName} size={.spec.resources.requests.storage} modes={.spec.accessModes}{"\n"}'

# Which class is the default? (empty claim class binds here)
kubectl get sc -o jsonpath='{range .items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")]}{.metadata.name}{"\n"}{end}'

# Pipeline gate: every rendered class must exist in the target cluster
# for c in $(grep -rhoP 'storageClassName:\s*\K\S+' rendered/); do
#   kubectl get sc "$c" >/dev/null || { echo "missing class: $c"; exit 1; }
# done
📊 Production Insight
One suffix (fast-ssd-prod vs fast-ssd) kept 14 claims unbound for 3 hours across a whole environment. A 5-second existence gate in the deploy pipeline would have failed the push instantly.
🎯 Key Takeaway
Class names match exactly or nothing provisions. List classes, compare letter-by-letter, standardize across envs, gate in CI.

Static Binding: Capacity, Modes, and the PV Table

Without dynamic provisioning, binding is a three-way match between claim and a pre-created PersistentVolume: the volume's capacity must be greater than or equal to the request (a 5Gi volume never satisfies a 10Gi claim, and Kubernetes won't partially fill), the claim's accessModes must be a subset of the volume's (claiming ReadWriteMany against RWO-only volumes fails), and storageClassName must agree on both sides. One mismatch and the claim waits forever — silently, since static matching emits few events. Read the PV table, not your assumptions.

Reclaim policy and phase add two more traps. A volume in Released phase (previously bound, claim deleted without wiping the volume) never rebinds automatically — it needs manual reclaim (retain-and-clean) or deletion and recreation. And accessModes semantics are driver-real: ReadWriteOnce means one node, so a claim needing multi-node writers genuinely needs RWX-capable storage (NFS, EFS, CephFS), not a bigger EBS volume. Asking EBS for RWX is asking the impossible, however the manifest phrases it.

The one-table diagnosis: kubectl get pv with custom columns for class, capacity, modes, and phase shows every candidate and its disqualifier at a glance. Fix the actual column — resize the request, align the modes with what the driver supports, match the class names, or recycle Released volumes. Static binding rewards table reading; every other approach is guessing.

static-pv-claim-match.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# A static PV and the claim that binds to it: class, size, modes agree.
# kubectl get pv -o custom-columns shows disqualifiers at a glance.
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-uploads-10gi
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Retain
  hostPath:
    path: /mnt/data-uploads
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: uploads
  namespace: staging
spec:
  storageClassName: manual   # must equal the PV's class (or both empty)
  accessModes:
    - ReadWriteOnce          # subset of the PV's modes
  resources:
    requests:
      storage: 5Gi           # <= the PV's 10Gi capacity
📊 Production Insight
Released-phase volumes are the quietest trap: everything matches but the phase says Released, and binding never retries. Recycle or recreate — matching fields alone won't revive it.
🎯 Key Takeaway
Capacity >= request, modes subset, classes equal. Read the PV table; fix the disagreeing column; watch for Released phase.

WaitForFirstConsumer vs Immediate: Timing, Not Failure

VolumeBindingMode splits provisioning into two philosophies. Immediate binds at claim creation — the volume exists before any pod, which is simple but zone-blind: the provisioner may carve storage in zone A while the pod later schedules in zone B, stranding both. WaitForFirstConsumer waits for a pod to need the claim, then provisions near the chosen node — correct topology, but the claim reads Unbound and the pod waits during the delay. Newcomers misread that wait as breakage and start deleting things that were working.

Know which mode your class uses before debugging: kubectl get sc <class> -o jsonpath for volumeBindingMode. Immediate plus unbound means a real failure (provisioner error or no candidates) — dig in. WaitForFirstConsumer plus unbound means scheduling is mid-flight — check the pod's scheduler events for the actual constraint (node affinity, taints, zone limits) rather than the storage. The storage resolves itself once scheduling picks a home.

Choose deliberately per workload. Stateful sets with node affinity and multi-zone clusters want WaitForFirstConsumer (it's the default in most provisioners' recommended classes). Single-zone dev clusters and pre-provisioned static volumes are fine with Immediate. Document the mode per class in your cluster README so the next on-call doesn't debug a normal delay — and set the Pending alert threshold past the normal bind window so it pages for stuck, not for slow.

binding-mode-check.shBASH
1
2
3
4
5
6
7
8
9
10
11
# Which timing philosophy does your class use?
kubectl get sc fast-ssd -o jsonpath='mode={.volumeBindingMode} provisioner={.provisioner}{"\n"}'

# WaitForFirstConsumer: unbound-during-scheduling is normal — check the pod side
kubectl describe pod -l app=postgres -n staging | grep -A6 'Events:' | tail -8

# Immediate: unbound means real failure — check provisioner events
kubectl get events -n staging --sort-by=.lastTimestamp | grep -iE 'provision|volume|pvc' | tail -10

# Allowed topologies can strand WaitForFirstConsumer claims
kubectl get sc fast-ssd -o jsonpath='{range .allowedTopologies}{@}{"\n"}{end}'
📊 Production Insight
WaitForFirstConsumer delays look exactly like failures to anyone watching get pvc output. Documenting the mode per class plus a 10-minute Pending threshold separates slow from stuck.
🎯 Key Takeaway
Immediate-unbound is failure; WaitForFirstConsumer-unbound is scheduling in progress. Check the mode, then read the right events.

Dynamic Provisioning: When the Clerk Never Shows

Dynamic provisioning needs a running provisioner: the CSI external-provisioner sidecar watching for claims in its class and calling the cloud API to carve volumes. If the driver isn't installed, the sidecar is crash-looping, IAM denies the CreateVolume call, or the cloud quota is exhausted, claims wait with ProvisioningFailed events — or no events if the class points at a provisioner name nothing serves. Reinstalling random drivers (the Wednesday response) fixes nothing when the installed one lacks permissions.

Debug the chain outside-in. The class's provisioner field names the expected driver; the driver namespace should show running controller pods; their logs carry the cloud-side verdict — AccessDenied, quota exceeded, invalid parameter — verbatim. IAM and quota dominate in cloud: a fresh cluster with an old node role, or a region that hit its volume cap, fails every claim identically. Cross-check by provisioning manually (a test claim) while tailing controller logs: the error appears within seconds.

Harden the path once it works. Pin CSI driver versions in cluster bootstrapping (not latest-at-install), grant least-privilege storage IAM with a documented policy, alert on provisioner pod restarts, and keep a test-claim job that binds and deletes weekly — a synthetic probe proving the whole chain end to end. Provisioning is a clerk, a phone line (API), and permission to order rooms; monitor all three and the lobby stays empty.

provisioner-chain-debug.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 1. Which driver should serve this class?
kubectl get sc fast-ssd -o jsonpath='{.provisioner}{"\n"}'

# 2. Is its controller alive? (labels vary by driver — adjust)
kubectl get pods -n kube-system | grep -i -E 'csi|provisioner|ebs|snapshot'
kubectl get pods -n kube-system -l app=csi-provisioner -o wide 2>/dev/null

# 3. The cloud-side verdict lives in controller logs
kubectl logs -n kube-system -l app=csi-provisioner --tail=80 2>/dev/null | grep -iE 'error|fail|denied|quota|invalid' | tail -10

# 4. Synthetic probe: a test claim while tailing logs
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: probe-bind-test
  namespace: default
spec:
  storageClassName: fast-ssd
  accessModes: [ReadWriteOnce]
  resources:
    requests:
      storage: 1Gi
EOF
kubectl get pvc probe-bind-test -w --timeout=60s
kubectl delete pvc probe-bind-test
📊 Production Insight
IAM denials and volume quotas cause fleet-wide identical failures that look like broken Kubernetes. Controller logs print the cloud verdict verbatim — read them before reinstalling anything.
🎯 Key Takeaway
Class names the driver, controllers must run, logs carry the cloud verdict. Probe with a test claim; monitor the chain.

Bind It and Keep It Bound

Resolution order never changes: fix the named cause (class spelling, size/modes, provisioner health), then watch binding complete on its own — no pod restarts needed, since the scheduler notices the bound claim and places the waiting pod within seconds. Verify with kubectl get pvc showing Bound plus a volume name, then kubectl get pods showing the workload leaving Pending. If binding completes but pods still wait, the remaining constraint is elsewhere (node resources, affinity) — storage did its job.

The deployment manifest below shows the correct end state for the common dynamic case: claim and pod agreeing on name, namespace, and modes, against a class that exists. Note the claim lives in the same namespace as the pod — cross-namespace claims don't exist, and a typo'd namespace orphans the reference silently. Apply, watch Bound, watch Running: that's the whole ceremony when inputs are right.

Lock it with four guards. The CI class-existence gate from the incident. Standardized class names across environments. A Pending-pods alert (10 minutes, with describe output) so waits page before they become outages. And a weekly test-claim probe proving provisioning end to end. Storage binding is paperwork with a clerk — verify the names, keep the clerk healthy, and pods stop waiting in the lobby.

bound-claim-and-pod.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# Correct end state: claim + pod agreeing on name, namespace, modes.
# Apply, watch Bound, watch Running — no restarts needed.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-postgres
  namespace: staging
spec:
  storageClassName: fast-ssd   # must exist: kubectl get sc
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: staging           # same namespace as the claim
spec:
  serviceName: postgres
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: data-postgres
📊 Production Insight
Binding completes on its own once inputs are right — the scheduler places waiting pods within seconds, no restarts needed. Teams that delete and recreate workloads during storage incidents only reset the wait.
🎯 Key Takeaway
Fix the named cause, watch Bound then Running. Guard with CI gates, standard names, Pending alerts, and weekly probes.
● Production incidentPOST-MORTEMseverity: high

A StorageClass Typo Left 14 Pods Pending for 3 Hours

Symptom
At 10:05 AM on a Wednesday, a fresh staging environment came up with all 14 stateful pods (Postgres, Redis, 12 workers with scratch volumes) stuck Pending: "pod has unbound immediate PersistentVolumeClaims." The manifests were copied from production, where the identical stack ran green. Engineers assumed the new cluster's CSI driver wasn't installed and spent an hour reinstalling it, then suspected zone capacity and spent another hour checking quotas — while the pods waited and the environment stayed dark.
Assumption
Because the manifests were "identical to production," the team assumed infrastructure, not inputs: missing CSI driver, disabled storage, wrong cloud permissions. Two engineers reinstalled the EBS CSI driver and verified IAM roles. The manifests were indeed nearly identical — except the new environment's values file set storageClassName: fast-ssd-prod while the new cluster only defined fast-ssd. One suffix, copied from a production overlay, invalidated all 14 claims.
Root cause
Every PVC requested a StorageClass that didn't exist in the new cluster, so the provisioner never engaged — there is no controller watching claims for unknown classes. The describe output said storageclass.storage.k8s.io fast-ssd-prod not found plainly, but the team read pod events (which only say unbound) instead of PVC events (which name the missing class) for the first 3 hours.
Fix
Immediate: corrected the values file to fast-ssd, reapplied — all 14 claims bound and pods started within 4 minutes. Same day: added a CI check that every storageClassName in rendered manifests exists in the target cluster (kubectl get sc lookup in the deploy pipeline). That month: standardized class names across environments so overlays can't diverge, and added a Pending-pods alert that fires after 10 minutes with describe output attached.
Key lesson
  • Read PVC events, not pod events. Pods say unbound; claims say why — the missing class name was printed the whole time, one describe away.
  • Copied manifests aren't identical manifests. Overlays and values files mutate inputs silently; diff rendered output against the target cluster, not against the source environment.
  • Gate deploys on referenced storage names. A 5-second StorageClass existence check in the pipeline beats 3 hours of driver reinstalls.
Production debug guideFive symptoms, five exact command sequences — start at the claim, not the pod.5 entries
Symptom · 01
Pods Pending with unbound claims and you don't know which storage input is wrong
→
Fix
Read the claim's own Events — they name the cause: run kubectl get pvc -A to find them, then kubectl describe pvc <name> -n <ns> and read the Events section at the bottom. storageclass not found means a bad class name; no persistent volumes available means static binding with no match; provisioner errors mean dynamic provisioning is failing. The Events text picks your next step.
Symptom · 02
Events say the StorageClass wasn't found, or binding should be dynamic but nothing provisions
→
Fix
List what exists with kubectl get sc and kubectl get pv, then compare letter-by-letter with the claim: kubectl get pvc <name> -o jsonpath='{.spec.storageClassName} {.spec.resources.requests.storage} {.spec.accessModes}'. A typo (fast-ssd-prod vs fast-ssd), an empty storageClassName (which means default — check which class is annotated default), or a missing provisioner field each has its own fix: correct the name, set the default, or install the driver.
Symptom · 03
Class exists but binding never happens — suspect the provisioner
→
Fix
Confirm the StorageClass provisioner (kubectl get sc <class> -o jsonpath='{.provisioner}') matches an installed driver: kubectl get pods -n kube-system -l app=csi-provisioner (adjust labels per driver) should show running controllers. Read their logs with kubectl logs -n kube-system -l app=csi-provisioner --tail=50 | grep -i -E 'error|fail' for IAM denials, quota hits, or API errors — cloud permission failures are the top cause here.
Symptom · 04
Static volumes exist but the claim won't bind to any of them
→
Fix
Compare all three binding inputs: capacity (volume >= claim request), accessModes (claim's modes must be a subset of the volume's), and storageClassName (both empty or both equal — a mismatch blocks binding silently). Run kubectl get pv -o custom-columns=NAME:.metadata.name,CLASS:.spec.storageClassName,CAP:.spec.capacity.storage,MODES:.spec.accessModes,STATUS:.status.phase to see every candidate in one table, then fix whichever column disagrees.
Symptom · 05
Claim uses WaitForFirstConsumer and stays unbound longer than expected
→
Fix
Check the mode first: kubectl get sc <class> -o jsonpath='{.volumeBindingMode}' — WaitForFirstConsumer binds only when a pod is scheduled, so unbound-during-scheduling is normal, not failure. If pods also can't schedule, read kubectl describe pod for the topology conflict (claim's zone vs node selector), and verify nodes exist in the provisioner's supported zones. Give scheduling a minute before treating the delay as a bug.
Unbound PVC Causes — Confirm, Fix, Prevent
Root CauseHow to ConfirmFixPrevention
StorageClass name wrong or missingdescribe pvc Events say class not found; name absent from kubectl get scCorrect storageClassName; set a default class per clusterCI gate: every rendered class must exist in target; standardize names
Capacity or accessModes mismatchPV table shows smaller capacity, incompatible modes, or class disagreementResize request; align modes with driver; match class on both sidesLint manifests against driver capabilities; document per-class limits
Provisioner down or deniedProvisioningFailed events; controller pods missing or logging IAM/quota errorsInstall/fix driver; grant storage IAM; raise quotaPin driver versions; alert on controller restarts; weekly test-claim probe
WaitForFirstConsumer timing misreadClass mode is WaitForFirstConsumer; pod scheduler events show placement in flightWait for scheduling; fix the scheduler constraint if one blocksDocument mode per class; set Pending alert past normal bind window
Released or orphaned static volumePV phase Released; claim waits though fields matchRecycle data and delete/recreate the PV, or release the claim properlySet reclaim policies deliberately; track static volumes as inventory
⚙ Quick Reference
5 commands from this guide
FileCommand / CodePurpose
storageclass-name-check.shkubectl get sc -o custom-columns=NAME:.metadata.name,PROVISIONER:.provisioner,MO...StorageClass Names
static-pv-claim-match.yamlapiVersion: v1Static Binding
binding-mode-check.shkubectl get sc fast-ssd -o jsonpath='mode={.volumeBindingMode} provisioner={.pro...WaitForFirstConsumer vs Immediate
provisioner-chain-debug.shkubectl get sc fast-ssd -o jsonpath='{.provisioner}{"\n"}'Dynamic Provisioning
bound-claim-and-pod.yamlapiVersion: v1Bind It and Keep It Bound

Key takeaways

1
Describe the PVC first
its Events name the cause that pod events hide.
2
Class names match exactly; empty means default, and no default means static-only.
3
Static binding needs capacity, modes subset, equal classes, Available phase.
4
Immediate-unbound is failure; WaitForFirstConsumer-unbound is scheduling in flight.
5
Provisioner chain
class names driver, controllers run, logs carry cloud verdict.
6
Gate classes in CI, standardize names, alert on Pending, probe weekly.

Common mistakes to avoid

6 patterns
×

Reading pod events instead of PVC events

Symptom
Hours on node and image theories while the claim's Events named the missing class the whole time.
Fix
Describe the PVC first for every unbound-claim Pending pod. Pods say waiting; claims say why.
×

Deleting and recreating the pod to fix storage

Symptom
Replacement pod waits identically — the claim is still unbound, so the restart only reset the wait clock.
Fix
Fix the storage input, then watch. Binding completes on its own and the scheduler places the existing pod.
×

Assuming copied manifests match the new cluster

Symptom
fast-ssd-prod requested where only fast-ssd exists; 14 claims dead on arrival in a fresh environment.
Fix
Diff rendered manifests against the target cluster's classes. Standardize class names across environments.
×

Treating WaitForFirstConsumer delay as failure

Symptom
Deleting claims mid-scheduling, which restarts the bind cycle and guarantees the wait never ends.
Fix
Check the binding mode first. During scheduling, watch pod events — storage resolves once placement completes.
×

Reinstalling the CSI driver for an IAM problem

Symptom
Fresh driver, same denials — the controller logs said AccessDenied before and after the reinstall.
Fix
Read controller logs before touching installs. IAM and quota are config, not binaries.
×

Claiming RWX from storage that can't do it

Symptom
EBS-class volumes requested ReadWriteMany wait forever — block storage is RWO by physics, not by config.
Fix
Match modes to the driver: RWO for block, RWX only on shared filesystems like NFS, EFS, or CephFS.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
A pod is Pending with unbound claims. What's your first command?
Q02JUNIOR
What must match for a claim to bind a static volume?
Q03SENIOR
How does WaitForFirstConsumer change what unbound means?
Q04SENIOR
All claims fail with ProvisioningFailed after a cluster rebuild. How do ...
Q05SENIOR
Design storage guardrails so a typo can't take down an environment again...
Q01 of 05JUNIOR

A pod is Pending with unbound claims. What's your first command?

ANSWER
kubectl describe pvc — the claim's Events name the cause (missing class, provisioner error, no candidates) while pod events only say waiting. Then get pvc for phase and age, and namespace events for the timeline.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
Will deleting the Pending pod fix an unbound claim?
02
Can a PVC bind a volume in another namespace?
03
Why did binding work in prod but not in the new environment?
04
How long should WaitForFirstConsumer binding take?
05
What's the difference between RWO and RWX in practice?
06
Should I pre-create volumes or rely on dynamic provisioning?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.

Follow
✓ Verified
production tested
September 23, 2026
last updated
1,942
articles · all by Naren
🔥

That's Kubernetes. Mark it forged?

6 min read · try the examples if you haven't

←
Previous
Docker Exec Format Error Fix
15 / 15 · Kubernetes
Next
HTTP 413 Request Entity Too Large Fix
→