Persistent Volume Claims (PVC)
A PersistentVolumeClaim (PVC) is how a Pod requests storage from the cluster. The developer specifies how much storage they need and what access mode is required; Kubernetes finds a matching PersistentVolume and binds the PVC to it. The developer does not need to know whether the storage is backed by NFS, a cloud disk, or a local directory — they just request storage and mount it. This separation of concerns is the design intent: administrators manage PVs, developers consume PVCs.
What is a PVC?
PVC binding process:
Developer creates PVC:
"I need 5Gi of ReadWriteOnce storage, storageClass=standard"
|
v
Kubernetes control plane searches for matching PV:
- capacity >= 5Gi
- accessMode = ReadWriteOnce
- storageClass = standard
|
match found? yes
|
v
PVC STATUS: Bound ←→ PV STATUS: Bound
(they are now exclusively linked)
|
v
Pod mounts PVC as a volumeCreating a PVC
nano mysql-pvc.yaml
mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: local-path # Use k3s default, or "standard" on cloud
kubectl apply -f mysql-pvc.yaml
# Check PVC status:
kubectl get pvc
kubectl get pvc output
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mysql-pvc Bound pvc-4a2b8c1d-e3f4-5678-abcd-ef1234567890 10Gi RWO local-path 15s
# STATUS = Bound means a PV was found and allocated
# If STATUS = Pending, either:
# - No matching PV exists (and no StorageClass for dynamic provisioning)
# - No StorageClass matches the requested storageClassName
# Debug:
kubectl describe pvc mysql-pvc # Shows events explaining why it is Pending
Using a PVC in a Pod
nano mysql-deployment.yaml
mysql-deployment.yaml — using the PVC
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
replicas: 1 # Only 1 replica for RWO volumes (one node can mount at a time)
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
volumeMounts:
- name: mysql-storage
mountPath: /var/lib/mysql # MySQL data directory
volumes:
- name: mysql-storage
persistentVolumeClaim:
claimName: mysql-pvc # Reference the PVC by name
StatefulSets and PVCs
# StatefulSets automatically create PVCs per replica using volumeClaimTemplates:
# (Used for databases, Kafka, Elasticsearch — anything that needs unique storage per replica)
nano mysql-statefulset.yaml
mysql-statefulset.yaml — automatic PVC per replica
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-cluster
spec:
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
# Creates: data-mysql-cluster-0, data-mysql-cluster-1, data-mysql-cluster-2
# Each replica gets its own dedicated PVC
Conclusion
For stateless applications, use regular Deployments with a single PVC. For stateful applications that need unique storage per replica (databases, message queues), use StatefulSets with volumeClaimTemplates — they automatically create and manage one PVC per Pod. Never run a database with replicas: 3 on an RWO PVC; all three replicas would fight over the same volume (only one can mount it). Either use RWX storage or use a StatefulSet with separate PVCs per replica.
Need help with Ubuntu administration?
Work directly with Muhammad Irfan Aslam for Ubuntu Server, Linux, cloud, Docker, DevOps, CI/CD, or infrastructure troubleshooting support.
Work with Muhammad Irfan Aslam