PV vs PVC vs StorageClass: Who Owns What in Kubernetes Storage

Quick answer
PersistentVolume, PersistentVolumeClaim, and StorageClass get explained as a trio so often that people miss the point: they exist to separate three different jobs owned by three different roles. Here's the mental model that makes the whole storage API click.
- The Lifecycle: Static vs Dynamic Provisioning
- Binding: The Part Everyone Gets Burned By
- Reclaim Policy: Where Data Goes to Die (or Not)
- Quick Reference
6 min read · Kubernetes
Every Kubernetes storage explainer lists the same three objects — PersistentVolume, PersistentVolumeClaim, StorageClass — and then immediately blurs them together. The result is that I still see experienced engineers kubectl edit-ing PVs by hand, hardcoding volume IDs into manifests, and asking why deleting a PVC sometimes deletes data and sometimes doesn't.
The model is simpler than the docs make it look: the three objects exist to separate three jobs, owned by three roles.
- A PersistentVolume (PV) is the supply: an actual piece of storage (an EBS volume, an NFS export, a Ceph image) represented as a cluster-scoped API object. Infrastructure's side of the contract.
- A PersistentVolumeClaim (PVC) is the demand: a namespaced request — "I need 20Gi, ReadWriteOnce" — written by the application team, who shouldn't need to know what an EBS volume is.
- A StorageClass is the vending machine: a template that says "when a claim asks for class
gp3, call this CSI driver with these parameters and create a PV automatically."
Pods never mount PVs directly. They reference PVCs, and the PVC binds to a PV. That indirection is the entire point — it's the same supplier/consumer split as Nodes and Pods.
The Lifecycle: Static vs Dynamic Provisioning
Static provisioning is the original flow: an admin pre-creates PVs, and PVCs bind to whichever existing PV matches capacity, access mode, and (optionally) storageClassName. Almost nobody should be doing this in 2026 outside of NFS shares and bare-metal edge cases.
Dynamic provisioning is what every managed cluster does: the PVC names a StorageClass, the CSI provisioner watches for the claim, creates the backing volume in the cloud, and creates the PV object for you. You never touch a PV manifest.
1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4 name: data
5spec:
6 accessModes: ["ReadWriteOnce"]
7 storageClassName: gp3
8 resources:
9 requests:
10 storage: 20GiThat's the whole application-side contract. If your app manifests contain awsElasticBlockStore or a volumeHandle, something has gone wrong organizationally, not just technically.
Binding: The Part Everyone Gets Burned By
A few rules that explain 90% of "my PVC is stuck in Pending" tickets:
Binding is exclusive and 1:1. One PVC binds one PV, even if the PV is bigger than the request. A 100Gi PV claimed by a 5Gi PVC wastes 95Gi.
storageClassName must match exactly — including the empty string. A PVC with storageClassName: "" explicitly requests static binding to a classless PV. A PVC with the field omitted gets the cluster's default StorageClass. These are different behaviors, and the difference is invisible until it bites you. Check what kubectl get storageclass marks as (default).
WaitForFirstConsumer delays provisioning until a pod schedules. With volumeBindingMode: WaitForFirstConsumer (the right setting for zonal disks like EBS), the PVC stays Pending until a pod actually uses it, so the volume gets created in the zone where the pod landed. If your PVC is Pending and there's no pod, that's not a bug — that's the mode working. I covered the scheduling interplay in Fix Kubernetes Pending Pods.
Access modes are about nodes, not pods. ReadWriteOnce means one node can mount the volume — multiple pods on that node can share it. If you actually need single-pod exclusivity, that's ReadWriteOncePod.
Kubernetes Production Readiness Checklist
The pre-launch checks we run before calling a cluster production-ready — probes, resources, RBAC, upgrades, and backups. Plain Markdown you can commit to your repo.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
Reclaim Policy: Where Data Goes to Die (or Not)
When a PVC is deleted, the PV's persistentVolumeReclaimPolicy decides the fate of the data:
Delete— the PV and the backing volume in your cloud account are destroyed. This is the default for dynamically provisioned volumes on most StorageClasses.Retain— the PV flips toReleasedand the data survives, but the PV won't rebind to a new claim until an admin manually clearsspec.claimRef. Released ≠ Available, and that surprises everyone the first time.
My production rule: databases and anything stateful-by-design get a StorageClass with reclaimPolicy: Retain; scratch and cache volumes keep Delete. Set this at the StorageClass level — fixing it per-PV after the fact is toil.
Quick Reference
| PersistentVolume | PersistentVolumeClaim | StorageClass | |
|---|---|---|---|
| Scope | Cluster | Namespace | Cluster |
| Owned by | Infra / provisioner | Application team | Platform team |
| Represents | Actual storage | Request for storage | Provisioning template |
| Created by (dynamic) | CSI provisioner | You | Platform, once |
| Pod references it? | Never directly | Yes, via volumes | Never |
See also
- Deploy Rook-Ceph on Kubernetes: Block and Object Storage from Scratch
- PersistentVolume vs PersistentVolumeClaim: The Difference, Explained Properly
Frequently Asked Questions
What is the difference between a PersistentVolume and a PersistentVolumeClaim?
A PersistentVolume is the cluster-scoped representation of real storage — it exists whether or not anyone is using it. A PersistentVolumeClaim is a namespaced request for storage by an application. The claim binds to a volume (1:1), and pods always mount the claim, never the volume. Think supply (PV) and demand (PVC).
Do I still need to create PVs manually?
Almost never. With dynamic provisioning, the StorageClass's CSI driver creates both the backing storage and the PV object when a PVC appears. Manual PV creation is only needed for storage that can't be provisioned on demand — pre-existing NFS exports, local disks on bare metal, or importing an existing cloud volume.
Why is my PVC stuck in Pending?
Three usual suspects: the StorageClass uses WaitForFirstConsumer and no pod references the claim yet (by design); the storageClassName doesn't match any class — including the omitted-vs-empty-string trap with default classes; or, with static provisioning, no Available PV satisfies the size, access mode, and class. kubectl describe pvc tells you which one within seconds.
Does deleting a PVC delete my data?
It depends on the bound PV's reclaim policy. Delete (the common default for dynamic volumes) destroys the backing volume too. Retain keeps the data, but the PV stays Released and unusable until an admin clears its claimRef. Check before you delete: kubectl get pv -o custom-columns=NAME:.metadata.name,POLICY:.spec.persistentVolumeReclaimPolicy.
For hands-on practice, work through the PVCs and StorageClasses tutorial and the PersistentVolumes lab; for production patterns (expansion, snapshots, topology), see the persistent volumes production guide.
Running stateful workloads and not sure your storage classes are set up safely? Get in touch.
Official References
- Persistent Volumes — access modes, reclaim policies and binding
- Storage Classes — provisioner parameters and volume binding modes
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


