PersistentVolumeClaim
A user's formal request for storage in Kubernetes that binds to an available PersistentVolume, abstracting the underlying storage provider and allowing pods to consume durable storage independently of their own lifecycle.
PersistentVolumeClaim — Extended Technical Detail
What is a PVC in Simple Terms?
Think of a PersistentVolume (PV) as a physical hard drive in the data centre. A PersistentVolumeClaim (PVC) is your ticket to reserve that hard drive for your pod. You say "I need 50GB of fast SSD storage" and Kubernetes finds a matching PV and binds them together.
PVC Lifecycle
+------------------------------------------+| Admin creates PersistentVolume | <- Static provisioning, or StorageClass| (or StorageClass auto-provisions one) | handles this automatically+------------------------------------------+ | v+------------------------------------------+| Developer creates PersistentVolumeClaim | <- Specifies size, access mode,| (requests storage size + type) | and StorageClass+------------------------------------------+ | v+------------------------------------------+| Kubernetes binds PVC to matching PV | <- Binding is exclusive — one PVC| | to one PV only+------------------------------------------+ | v+------------------------------------------+| Pod mounts the PVC as a volume | <- Pod references PVC by name| | in its volumes spec+------------------------------------------+ | v+------------------------------------------+| Data persists even if pod restarts | <- Survives pod deletion, rescheduling,| or gets deleted | and node replacement+------------------------------------------+Access Modes Explained
+------------------------+ +------------------------+ +------------------------+| ReadWriteOnce (RWO) | | ReadOnlyMany (ROX) | | ReadWriteMany (RWX) || | | | | || One node can read | | Many nodes can read | | Many nodes can read || and write | | simultaneously | | AND write || | | | | || Use for: databases | | Use for: static | | Use for: shared file || (MySQL, Postgres) | | config, read caches | | storage (NFS, EFS) |+------------------------+ +------------------------+ +------------------------+Example PVC and Pod
# pvc.yaml — request 50GB SSD storage for a MySQL databaseapiVersion: v1kind: PersistentVolumeClaimmetadata: name: mysql-data-pvc namespace: productionspec: accessModes: - ReadWriteOnce # Only one node can mount this for read-write storageClassName: gp3-encrypted resources: requests: storage: 50Gi---# pod.yaml — mount the PVC inside the MySQL containerspec: containers: - name: mysql image: mysql:8.0 volumeMounts: - name: mysql-storage mountPath: /var/lib/mysql # MySQL data directory inside the container volumes: - name: mysql-storage persistentVolumeClaim: claimName: mysql-data-pvc # Reference the PVC by nameStorageClass and Dynamic Provisioning
Most production clusters use dynamic provisioning — no admin needs to pre-create PVs. The StorageClass defines the provisioner and disk type:
# storageclass.yaml — production-grade encrypted SSD StorageClass for AWSapiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: gp3-encryptedprovisioner: ebs.csi.aws.comparameters: type: gp3 encrypted: "true" iops: "3000" throughput: "125"reclaimPolicy: Retain # CRITICAL: Retain disk after PVC deletionallowVolumeExpansion: true # Allow resizing without pod restartvolumeBindingMode: WaitForFirstConsumer # Provision in the same AZ as the podTipAlways use a StorageClass with
reclaimPolicy: Retainfor production databases. This prevents the underlying disk from being automatically deleted if the PVC is accidentally removed. You can always manually delete the PV after confirming the data is no longer needed.
Checking PVC Status and Binding
# List all PVCs across all namespaceskubectl get pvc -A # Check PVC binding status and which PV it's bound tokubectl get pvc mysql-data-pvc -n production# NAME STATUS VOLUME CAPACITY ACCESS MODES# mysql-data-pvc Bound pvc-3a8f2c1d-4b5e-11ee-9a2f-0a1b2c3d4e5f 50Gi RWO # Describe for full details including eventskubectl describe pvc mysql-data-pvc -n production # Check if PV is retained after PVC deletionkubectl get pv | grep Released# A Released PV can be manually reclaimed and reboundResizing a PVC
# Step 1: Edit the PVC to request more storage (StorageClass must allow expansion)kubectl patch pvc mysql-data-pvc -n production \ -p '{"spec":{"resources":{"requests":{"storage":"100Gi"}}}}' # Step 2: The CSI driver expands the underlying disk automatically# Step 3: Verify the resize completedkubectl get pvc mysql-data-pvc -n production# Capacity should now show 100GiTroubleshooting Common PVC Problems
| Problem | Symptom | Fix |
|---|---|---|
| PVC stuck in Pending | STATUS: Pending indefinitely |
No matching PV or StorageClass — check kubectl describe pvc events for the exact mismatch |
| Pod stuck in ContainerCreating | Pod never starts | PVC is not yet bound — check kubectl get pvc and ensure STATUS is Bound |
| PVC deleted, data lost | Data unrecoverable | StorageClass had reclaimPolicy: Delete — switch to Retain for all production StorageClasses |
| PVC resize fails | Capacity unchanged after patch | StorageClass does not have allowVolumeExpansion: true — update the StorageClass and retry |
| Wrong AZ binding | Pod and PV in different AZs | Set volumeBindingMode: WaitForFirstConsumer on StorageClass to pin PV to pod's AZ |
Common MistakeUsing
accessModes: ReadWriteManyfor databases. Most cloud block storage (AWS EBS, GCP Persistent Disk) does not support RWX mode — the PVC will stay in Pending forever. Use RWX only for shared file storage like NFS or AWS EFS.
SecurityNever store Kubernetes Secrets or TLS certificates inside a PVC. Use the Secret object or an external secrets manager (AWS Secrets Manager, Vault). A PVC with
ReadWriteManyon a shared NFS mount means every pod in the cluster with the right claim can read every file on that volume.
RememberA PVC is namespace-scoped, but a PV is cluster-scoped. A PVC in
payments-prodcan only bind to a cluster-level PV — it cannot bind to a PV in another namespace. This is why StorageClass dynamic provisioning exists: it creates a fresh PV per PVC automatically without admin involvement.
Frequently Asked Questions
How does a PersistentVolumeClaim actually get matched to storage?
A PVC declares requirements — size, access mode, optionally a StorageClass — and the control plane binds it to a PersistentVolume that satisfies them. With static provisioning, an admin pre-creates PVs and the PVC binds to an existing match. With dynamic provisioning (the common case today), the StorageClass's provisioner creates a brand-new PV on demand — an EBS volume, a GCE disk, etc. — sized exactly to the claim, and binds it automatically.
Why does deleting a pod leave the underlying disk behind?
That's the point of PVCs — they decouple storage lifecycle from pod lifecycle. Deleting or rescheduling a pod doesn't touch its PVC, and deleting the PVC itself doesn't necessarily delete the disk, depending on the StorageClass's reclaim policy (Retain vs Delete). A common gotcha: with Retain, orphaned cloud disks pile up silently after teams delete namespaces, quietly inflating the cloud bill until someone audits unattached volumes.