Skip to main content

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

◈ DIAGRAM
+------------------------------------------+
| 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

◈ DIAGRAM
+------------------------+ +------------------------+ +------------------------+
| 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

YAML
# pvc.yaml — request 50GB SSD storage for a MySQL database
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-data-pvc
namespace: production
spec:
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 container
spec:
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 name

StorageClass and Dynamic Provisioning

Most production clusters use dynamic provisioning — no admin needs to pre-create PVs. The StorageClass defines the provisioner and disk type:

YAML
# storageclass.yaml — production-grade encrypted SSD StorageClass for AWS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-encrypted
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
iops: "3000"
throughput: "125"
reclaimPolicy: Retain # CRITICAL: Retain disk after PVC deletion
allowVolumeExpansion: true # Allow resizing without pod restart
volumeBindingMode: WaitForFirstConsumer # Provision in the same AZ as the pod
Tip

Always use a StorageClass with reclaimPolicy: Retain for 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

Bash
# List all PVCs across all namespaces
kubectl get pvc -A
# Check PVC binding status and which PV it's bound to
kubectl 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 events
kubectl describe pvc mysql-data-pvc -n production
# Check if PV is retained after PVC deletion
kubectl get pv | grep Released
# A Released PV can be manually reclaimed and rebound

Resizing a PVC

Bash
# 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 completed
kubectl get pvc mysql-data-pvc -n production
# Capacity should now show 100Gi

Troubleshooting 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 Mistake

Using accessModes: ReadWriteMany for 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.

Security

Never 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 ReadWriteMany on a shared NFS mount means every pod in the cluster with the right claim can read every file on that volume.

Remember

A PVC is namespace-scoped, but a PV is cluster-scoped. A PVC in payments-prod can 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.