What a Pending PVC means
A PersistentVolumeClaim sits in Pending because nothing has bound a PersistentVolume
to it. A control loop in the control plane watches for new PVCs and "finds a matching
PV (if possible), and binds them together"; until that match exists, the claim
remains unbound.
A PVC binds in one of two ways: an existing PV that satisfies the claim (static), or a
StorageClass that provisions one on demand (dynamic). When neither happens, the claim
stays Pending and any Pod that mounts it cannot start.
The single most useful command is kubectl describe pvc — read its Events. The event
message tells you which of the mechanisms below you are in, and they do not share a fix.
One distinction first, because it changes everything: a Pending PVC has never bound, so there is nothing to mount. That is not the same as a Pod stuck in ContainerCreating, where an already-bound volume fails to attach or mount on the node, nor the same as a Pod that will not schedule. Confirm the PVC is the thing that is stuck before you touch the workload.
Diagnose it
kubectl get pvc -n <namespace>
kubectl describe pvc <claim> -n <namespace>
kubectl get storageclass
kubectl get pv
kubectl get pvc shows STATUS Pending and an empty VOLUME column. The decisive
signal is the Events block from kubectl describe pvc. Match the message:
# No StorageClass / no default to provision from
no persistent volumes available for this claim and no storage class is set
# WaitForFirstConsumer — this is NORMAL, not a failure
waiting for first consumer to be created before binding
# Provisioner missing or name typo — nothing is listening
storageclass.storage.k8s.io "fast-ssd" not found
# or the PVC has a provisioner annotation but no controller acts on it
# Dynamic provisioning attempted but failed (quota, capacity, topology)
failed to provision volume with StorageClass "...": ...
# Static binding: no PV matches size / access mode / class
no persistent volumes available for this claim
kubectl get storageclass tells you whether a class exists and which one is marked
(default). kubectl get pv tells you whether any static PV is Available to bind.
Hold those three outputs side by side and the cause is usually obvious.
Causes, and how to fix each
Each mechanism is independent. Identify the event, then apply only that fix.
1. No StorageClass named, or no default StorageClass set
If a PVC omits storageClassName, Kubernetes uses the
default StorageClass —
the one annotated storageclass.kubernetes.io/is-default-class: "true". With no default
and no explicit class, dynamic provisioning never starts and the claim stays Pending. A
PVC that requests the class "" deliberately
disables dynamic provisioning
for itself.
- Diagnose:
kubectl get storageclassshows no row marked(default), or thestorageClassNamein your PVC names a class that does not exist. The event readsno persistent volumes available for this claim and no storage class is set. - Fix: mark one class default with the
storageclass.kubernetes.io/is-default-classannotation, or add an explicitstorageClassNameto the PVC. Note the blast radius: only have one class marked default — if two are, Kubernetes "uses the most recently created default StorageClass". - Newer clusters: since Kubernetes v1.26 the retroactive default StorageClass feature assigns a default to PVCs that were created before any default existed; older clusters leave them "stuck" Pending forever and you must recreate or patch the claim.
2. WaitForFirstConsumer — Pending is the intended state
This is the one case where Pending is correct. A StorageClass with
volumeBindingMode: WaitForFirstConsumer
"will delay the binding and provisioning of a PersistentVolume until a Pod using the
PersistentVolumeClaim is created." Until a Pod that mounts the claim is scheduled, the PVC
is supposed to stay Pending so the volume lands in the right topology.
- Diagnose:
kubectl describe pvcevent readswaiting for first consumer to be created before binding.kubectl get storageclass <name> -o yamlshowsvolumeBindingMode: WaitForFirstConsumer. If no Pod references the claim yet, there is no bug. - Fix: schedule a Pod that mounts the PVC — binding follows. If a Pod already exists
but the PVC still will not bind, the consumer cannot schedule (see causes 6 and 7), or
the Pod uses
nodeName: the docs warn that withWaitForFirstConsumer, usingnodeNamebypasses the scheduler and "PVC will remain in pending state". RemovenodeNameand use node affinity instead.
3. No CSI driver / provisioner installed, or a provisioner-name typo
A StorageClass must name a
provisioner —
"this field must be specified" — and for an external CSI provisioner, "a CSI driver must
be deployed on your cluster to use a csi volume"
(CSI volumes). If the driver
was never installed, or the provisioner string is misspelled, nothing acts on the claim
and it stays Pending with no error — because no controller is listening for it.
- Diagnose:
kubectl get storageclassshows the class, but no provisioning event ever appears onkubectl describe pvc(silence, not failure). Confirm the CSI controller is running, for examplekubectl get pods -n kube-system | grep csi, and compare the StorageClassprovisionervalue against the driver's real name (for exampleebs.csi.aws.comorpd.csi.storage.gke.io). - Fix: install the CSI driver, or correct the
provisionerfield to the driver's exact name. A StorageClass is immutable, so fixing a typo means deleting and recreating the class (and any PVC that referenced the wrong one).
4. Static provisioning — no PersistentVolume matches the claim
With static provisioning an administrator pre-creates PVs. The claim binds only to a PV that satisfies size, access modes, and storageClassName. The docs are explicit: "a cluster provisioned with many 50Gi PVs would not match a PVC requesting 100Gi" (binding), and claims "will remain unbound indefinitely if a matching volume does not exist."
- Diagnose:
kubectl get pvshows PVs that areAvailablebut none whose capacity is equal to or greater than the request, whose access modes cover the request, and whosestorageClassNamematches. The event readsno persistent volumes available for this claim. - Fix: create a PV that matches all three of size (at least the requested capacity), access modes, and storageClassName, or shrink the PVC request to fit an existing PV.
5. Access-mode mismatch (RWO vs RWX)
The four access modes are ReadWriteOnce, ReadOnlyMany, ReadWriteMany, and ReadWriteOncePod. Not every backend supports every mode — block storage like AWS EBS or GCE PD is typically ReadWriteOnce only, while ReadWriteMany usually needs a shared filesystem such as NFS or a clustered file system. If the PVC requests RWX from a backend that only offers RWO, no PV matches and provisioning fails.
- Diagnose: compare the PVC
accessModeswith what the backend supports. For static PVs,kubectl get pvshows the PVACCESS MODEScolumn; for dynamic, check the driver's documentation. The event isno persistent volumes available for this claimor afailed to provisionmessage naming the mode. - Fix: request a mode the backend supports (use RWO for single-node block volumes), or switch to a backend that genuinely supports RWX. Do not assume a CSI driver supports RWX just because the API accepts the field — the API validates syntax, not capability.
6. Topology / zone mismatch
A block volume lives in one availability zone. With Immediate binding the docs warn that
PVs are "bound or provisioned without knowledge of the Pod's scheduling requirements. This
may result in unschedulable Pods" — the volume is stranded in a zone the Pod cannot reach,
so either the PVC provisions into the wrong zone or the consumer Pod cannot schedule.
- Diagnose: the volume provisioned but the Pod that mounts it stays unschedulable;
compare the node zone labels with the volume's zone. For static PVs, an
allowedTopologies/node-affinity mismatch keeps the claim from binding to a usable PV. - Fix: prefer
WaitForFirstConsumerso the volume is provisioned in the zone the Pod is scheduled to, rather than provisioned first and stranded. Where you must pin zones, setallowedTopologieson the StorageClass.
7. Quota or capacity — the provisioner cannot satisfy the request
Dynamic provisioning can fail when the backend cannot create a volume of the requested size: a cloud account quota, a storage-pool capacity limit, or a per-volume size ceiling. The provisioner attempts, fails, and the PVC stays Pending.
- Diagnose: the event reads
failed to provision volume with StorageClass "...":followed by the backend error (quota exceeded, insufficient capacity, invalid size). The provisioner retries, so the event repeats. - Fix: raise the backend quota or free capacity, or reduce the requested size to within limits. The provisioner retries automatically once the backend can satisfy the request — you do not need to recreate the PVC unless its requested size is itself invalid.
Fix it
- Run
kubectl describe pvc <claim> -n <namespace>and read the first event. - Classify it: no/typo StorageClass, WaitForFirstConsumer (normal), missing provisioner, no matching static PV, access-mode mismatch, topology mismatch, or quota/capacity.
- If the event is
waiting for first consumer, stop — schedule a Pod that mounts the claim and binding follows. Nothing else is wrong. - Otherwise apply the single matching fix. Remember a StorageClass is immutable: fixing a provisioner or binding-mode typo means recreating the class and the affected PVCs.
- Confirm the PVC reaches
Boundwithkubectl get pvcbefore expecting the Pod to mount it. Binding must succeed before attach/mount even begins.
How Intellira diagnoses this
Intellira reads the PVC events through the read-only Kubernetes MCP server and classifies
the stall on the first pass — separating the one benign case (WaitForFirstConsumer waiting
for a consumer) from the genuine faults (no default StorageClass, missing or misnamed
provisioner, no matching PV, access-mode or topology mismatch, quota). It then correlates
the StorageClass and PVC manifests against the change that introduced them: the ArgoCD sync
or the Bitbucket commit that added the claim, renamed the storageClassName, or flipped
volumeBindingMode. That turns "PVC is Pending" into "this commit set
storageClassName: fast-ssd, but no StorageClass by that name exists in the cluster." The
agent never deletes or recreates storage objects — it points you at the binding decision
and the change that broke it, and leaves the apply to you.
Sources
- Persistent Volumes — binding, provisioning, access modes (Kubernetes)
- Storage Classes — volumeBindingMode, default class, provisioner, allowedTopologies (Kubernetes)
- Volumes — CSI (Kubernetes)
- Configure a Pod to Use a PersistentVolume for Storage (Kubernetes)
- Kubernetes v1.26: Retroactive Default StorageClass (Kubernetes blog)
By Intellira Engineering. AI-assisted draft; claims cited inline; last verified 2026-06-02. Pending technical review.