Persistent Storage — PV, PVC & StorageClass
IntermediateKubernetes PersistentVolumes (PV) and PersistentVolumeClaims (PVC) decouple storage provisioning from consumption. StorageClasses enable dynamic provisioning of cloud volumes (EBS, GCE PD, Azure Disk).
Overview
Pods are ephemeral — their filesystems vanish when the pod dies. For stateful workloads (databases, file storage), you need Persistent Volumes. The K8s storage model has three layers: PersistentVolume (PV) — a piece of actual storage (AWS EBS, NFS share, local disk) provisioned by an admin or dynamically by a StorageClass. PersistentVolumeClaim (PVC) — a pod's request for storage of a certain size and access mode. StorageClass — defines how PVs are dynamically provisioned (which cloud provider, IOPS, type). Dynamic provisioning is the standard on cloud — you create a PVC and K8s automatically provisions the cloud volume.
PersistentVolumeClaim — Requesting Storage
In cloud environments, you usually just create a PVC and let the StorageClass dynamically provision the underlying volume. You never have to manually create the PV.
# pvc.yaml — request 20 GB of SSD storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
spec:
accessModes:
- ReadWriteOnce # RWO: mounted read-write by ONE node at a time
# ReadWriteMany # RWX: mounted by multiple nodes (NFS, EFS)
# ReadOnlyMany # ROX: mounted read-only by multiple nodes
storageClassName: gp3 # which StorageClass to use (cloud-specific)
resources:
requests:
storage: 20Gi # request 20 GB
# K8s automatically provisions an AWS EBS gp3 volume and binds it to this PVC
# Use PVC in a Deployment
spec:
template:
spec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data # references the PVC above
# Check PVC status
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
# postgres-data Bound pvc-abc123 20Gi RWO gp3StorageClass — Dynamic Provisioning
StorageClasses define how storage is provisioned. Cloud providers ship default StorageClasses. You can create custom ones with specific disk types, IOPS, or reclaim policies.
# List available StorageClasses
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
# gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer
# gp3 ebs.csi.aws.com Delete WaitForFirstConsumer
# standard kubernetes.io/gce-pd Delete Immediate
# Custom StorageClass — AWS EBS io2 (high IOPS for databases)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: io2-database
provisioner: ebs.csi.aws.com
parameters:
type: io2
iopsPerGB: "50" # 50 IOPS per GB → 20GB PVC = 1000 IOPS
encrypted: "true"
reclaimPolicy: Retain # Retain: keep volume after PVC deletion
volumeBindingMode: WaitForFirstConsumer # provision only when pod is scheduled
# reclaimPolicy:
# Delete → volume deleted when PVC is deleted (default) — âš ï¸ data loss!
# Retain → volume kept after PVC deletion, must be manually reclaimed
# Recycle → deprecated
# Expand a PVC (must be supported by StorageClass)
kubectl patch pvc postgres-data -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'
# âš ï¸ EBS volumes require pod restart to see expanded size on some versionsStatefulSet — For Stateful Applications
Deployments are for stateless apps. StatefulSets are for databases and clustered apps — they provide stable pod names, stable network identities, and ordered startup/shutdown.
# StatefulSet provides:
# 1. Stable pod names: pod-0, pod-1, pod-2 (not random like Deployment)
# 2. Stable DNS: pod-0.my-service.default.svc.cluster.local
# 3. Ordered startup: pod-0 ready → pod-1 starts → pod-2 starts
# 4. Ordered shutdown: pod-2 terminates → pod-1 → pod-0
# 5. VolumeClaimTemplates: each pod gets its OWN PVC
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres # headless service name for DNS
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # each pod gets its own PVC!
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: gp3
resources:
requests:
storage: 20Gi
# Creates: data-postgres-0, data-postgres-1, data-postgres-2
# Each pod has its own 20 GB volume — not sharedKey Points to Remember
- 1PVC is the request for storage; PV is the actual storage; StorageClass handles dynamic provisioning.
- 2ReadWriteOnce (RWO): one node at a time — for databases. ReadWriteMany (RWX): multiple nodes — for shared file storage.
- 3reclaimPolicy: Delete destroys the volume when PVC is deleted — use Retain for production databases.
- 4StatefulSets give pods stable names (pod-0, pod-1) and each pod its own PVC via volumeClaimTemplates.
- 5Deployments = stateless; StatefulSets = stateful (databases, caches with persistence).
- 6Dynamic provisioning on cloud means you only create a PVC — K8s creates the EBS/GCE/Azure disk automatically.
Interview Questions
Sign in to ask AriaWhat is the difference between a Deployment and a StatefulSet?
What happens to the PVC when a StatefulSet pod is deleted and rescheduled?
Ask Aria about Persistent Storage — PV, PVC & StorageClass
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.