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 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:
YAML
# 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 podPLACEMENT PRO TIP**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 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
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 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 MISTAKE / WARNING**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.
COMMON MISTAKE / WARNING**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 THIS**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.