Kubernetes Day 9: PV, PVC & StorageClass Explained Simply

☸️ Day 9 — Storage

Kubernetes Day 9: PV, PVC & StorageClass

Kubernetes storage concepts explained simply — step by step, with examples

📅 March 2026  ·  ☸️ Kubernetes Preparation Series  ·  Day 9

#Kubernetes #PersistentVolume #PVC #StorageClass #K8sStorage #DevOps #CKAPrep #LearnK8s

1. The Problem — Why Pods Need Persistent Storage

Every Pod in Kubernetes has a temporary filesystem. When the Pod dies — because it crashed, was deleted, or was rescheduled to another node — everything written to that filesystem is gone forever. The next Pod starts fresh, like a brand new machine.

For stateless apps like web servers or APIs, this is fine. But what about:

  • A database — PostgreSQL, MySQL, MongoDB — which needs to keep its data across restarts
  • A file upload service — which saves user files that must persist
  • A message queue — like Kafka or RabbitMQ — which stores messages on disk
  • A logging system — which writes logs that must survive Pod restarts
🚨 Real scenario: You deploy a PostgreSQL database in Kubernetes. It runs fine. The node it is on restarts. The Pod is rescheduled to a different node. All your data is gone — because it was on the old node's local disk. Without persistent storage, this is exactly what happens.
🔑 The solution: Kubernetes storage is decoupled from Pods. Storage lives independently — outside any specific Pod or node. When a Pod needs storage, it claims a piece of it. When the Pod dies and is recreated, the same storage is remounted. Data persists across Pod restarts, rescheduling, and even node failures.

2. The 3 Storage Concepts

Kubernetes storage is built around three objects that work together. Understanding each one separately first makes the whole picture clear.

💾

Persistent Volume (PV)

The actual storage resource in the cluster. Provisioned by an admin or automatically. It represents a real piece of storage — an AWS EBS disk, a GCP Persistent Disk, an NFS share, or a local directory. It exists independently of any Pod.

📋

Persistent Volume Claim (PVC)

A request for storage by a user or application. The developer does not need to know the details of the underlying storage — they just say "I need 10Gi of ReadWriteOnce storage." Kubernetes matches this claim to a suitable PV.

⚙️

StorageClass

Defines the type and quality of storage available. It enables dynamic provisioning — when a PVC is created, the StorageClass automatically provisions a new PV. No admin needs to pre-create storage manually.

🏢 Simple analogy: Think of a hotel. The PV is the actual hotel room (real storage that exists). The PVC is your booking request ("I need a room with a double bed for 2 nights"). The StorageClass is the room category — Standard, Deluxe, Suite — which determines what kind of room you get and whether the hotel can build more rooms on demand.
☸️ How PV, PVC & StorageClass connect
Pod
uses volume
→
PVC
claims storage
→
PV
actual storage
→
Physical Disk
EBS / GCP PD / NFS
PVC
→
StorageClass
auto-provisions PV
→
PV (new)
created on demand
Top row: static provisioning (admin creates PV manually)  |  Bottom row: dynamic provisioning (StorageClass creates PV automatically)

3. Persistent Volume (PV) — The Actual Storage

A Persistent Volume is a piece of storage provisioned in the cluster. It is a cluster-level resource — not tied to any specific namespace. An administrator creates PVs (or a StorageClass creates them automatically). The PV represents the real storage backend: an AWS EBS volume, a GCP Persistent Disk, an Azure Disk, an NFS server, or even a local directory on a node.

PV lifecycle phases

A PV moves through these states during its lifetime:

Available Not yet claimed by any PVC — ready to be used. Bound Claimed by a PVC — in use. Released PVC was deleted — PV still exists, waiting for reclaim policy action. Failed Automatic reclamation failed.

PV YAML — static provisioning

 persistent-volume.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: my-pv spec: capacity: storage: 10Gi # total size of this volume accessModes: - ReadWriteOnce # can be mounted by one node at a time persistentVolumeReclaimPolicy: Retain # keep data after PVC deleted storageClassName: manual # links to PVC with same storageClassName hostPath: # storage backend (local dir for Minikube) path: /mnt/data # in production use: awsElasticBlockStore, # gcePersistentDisk, nfs, azureDisk, etc.

4. Persistent Volume Claim (PVC) — The Storage Request

A PVC is how an application requests storage. The developer does not need to know which physical disk to use, which cloud provider to call, or how the storage is provisioned. They just describe what they need — size, access mode, and optionally a storage class — and Kubernetes finds a matching PV and binds it.

A PVC is namespace-scoped — unlike PVs which are cluster-wide. The PVC lives in the same namespace as the Pod that uses it.

PVC YAML

 persistent-volume-claim.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc namespace: default # PVC is namespace-scoped spec: accessModes: - ReadWriteOnce # must match the PV's access mode storageClassName: manual # must match PV's storageClassName resources: requests: storage: 5Gi # requesting 5Gi — PV of 10Gi qualifies

Using a PVC in a Pod

 pod-with-pvc.yaml
apiVersion: v1 kind: Pod metadata: name: postgres-pod spec: containers: - name: postgres image: postgres:15 env: - name: POSTGRES_PASSWORD value: "mysecretpassword" volumeMounts: - name: postgres-storage mountPath: /var/lib/postgresql/data # where postgres stores its data volumes: - name: postgres-storage persistentVolumeClaim: claimName: my-pvc # reference the PVC by name
✅ When this Pod restarts or is rescheduled — the same PVC is remounted. The data in /var/lib/postgresql/data survives. This is the fundamental difference between ephemeral and persistent storage in Kubernetes.

5. StorageClass — Dynamic Provisioning

With static provisioning, an admin must manually create a PV before a developer can create a PVC. In large clusters with many teams, this becomes a bottleneck. StorageClass solves this with dynamic provisioning — a PV is automatically created the moment a PVC is submitted.

A StorageClass defines:

  • Provisioner — which storage plugin creates the volume (AWS EBS, GCP PD, Azure Disk, etc.)
  • Parameters — disk type, IOPS, encryption, zone settings
  • Reclaim policy — what happens to the PV when the PVC is deleted
  • Volume binding mode — when to provision (immediately or when a Pod is scheduled)

StorageClass examples

 storageclass-aws.yaml
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: kubernetes.io/aws-ebs # uses AWS EBS to create disks parameters: type: gp3 # SSD disk type encrypted: "true" # encrypt the disk at rest reclaimPolicy: Delete # delete EBS volume when PVC is deleted volumeBindingMode: WaitForFirstConsumer # provision when Pod is scheduled allowVolumeExpansion: true # allow resizing the PVC later
 pvc-dynamic.yaml — using the StorageClass
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: db-storage-claim spec: storageClassName: fast-ssd # use our StorageClass — triggers dynamic provisioning accessModes: - ReadWriteOnce resources: requests: storage: 20Gi # StorageClass auto-provisions a 20Gi EBS disk
💡 Default StorageClass: Most managed Kubernetes clusters (EKS, GKE, AKS) ship with a default StorageClass already configured. If your PVC does not specify a storageClassName, it automatically uses the default. On Minikube, the default StorageClass is standard using hostPath.
# See available StorageClasses in your cluster kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE standard (default) k8s.io/minikube-hostpath Delete Immediate # On EKS — you would see something like: gp2 (default) kubernetes.io/aws-ebs Delete WaitForFirstConsumer

6. How PV, PVC & StorageClass Work Together

Static provisioning flow (manual PV)

  1. Admin creates a PV — defines the actual storage size, type, and access mode.
  2. Developer creates a PVC — requests storage matching the PV's size and access mode.
  3. Kubernetes binds the PVC to the best matching PV — PV status changes to Bound.
  4. Developer references the PVC in their Pod spec — the volume is mounted.
  5. Pod restarts or is rescheduled → the same PVC is remounted → data persists.

Dynamic provisioning flow (with StorageClass)

  1. Admin creates a StorageClass once — defines the provisioner and disk type.
  2. Developer creates a PVC referencing the StorageClass.
  3. Kubernetes calls the StorageClass provisioner → new PV is automatically created in the cloud.
  4. PVC is bound to the new PV automatically.
  5. Developer references the PVC in their Pod — done. No admin involvement needed.
✅ Dynamic provisioning is the production standard. In real cloud clusters, you almost never create PVs manually. You define a StorageClass once, and every PVC using it gets its own dedicated cloud disk automatically.

7. Access Modes and Reclaim Policies

Access Modes — who can mount the volume?

ReadWriteOnce (RWO)

Mounted as read-write by a single node. Most common. Used for databases and single-replica apps. Supported by most storage backends.

ReadOnlyMany (ROX)

Mounted as read-only by many nodes simultaneously. Good for shared config or static content that many Pods need to read but not write.

ReadWriteMany (RWX)

Mounted as read-write by many nodes simultaneously. Requires a shared filesystem like NFS or cloud file storage (EFS, GCS Filestore). Not supported by most block storage.

ReadWriteOncePod (RWOP)

Mounted as read-write by a single Pod only — even stricter than RWO. Added in K8s v1.22. Guarantees only one Pod cluster-wide writes to this volume.

Reclaim Policies — what happens when a PVC is deleted?

Retain

PV and data are kept after PVC deletion. Admin must manually reclaim or delete the PV. Use for important data you never want to lose accidentally.

Delete

PV and the underlying storage (e.g. EBS disk) are deleted automatically when the PVC is deleted. Default for dynamically provisioned volumes. Be careful — data is gone.

Recycle (deprecated)

Data is scrubbed (basic rm -rf) and the PV is made available again. Deprecated in favour of dynamic provisioning. Do not use in new clusters.

⚠️ Delete policy in production: If your StorageClass uses reclaimPolicy: Delete (the default for most cloud StorageClasses), deleting a PVC permanently deletes the cloud disk and all its data. Always back up important data before deleting PVCs in production.

8. Full Hands-on Demo — Step by Step

Let us create a PV, a PVC, and a Pod that writes data to persistent storage on Minikube.

Step 1 — Create the PV

kubectl apply -f persistent-volume.yaml persistentvolume/my-pv created kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS STORAGECLASS my-pv 10Gi RWO Retain Available manual

Step 2 — Create the PVC

kubectl apply -f persistent-volume-claim.yaml persistentvolumeclaim/my-pvc created kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS my-pvc Bound my-pv 10Gi RWO manual ↑ automatically bound to my-pv ✅ # PV status also changed kubectl get pv NAME CAPACITY STATUS CLAIM STORAGECLASS my-pv 10Gi Bound default/my-pvc manual

Step 3 — Create a Pod that writes to the PVC

kubectl apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: storage-demo-pod spec: containers: - name: demo image: busybox command: ["sh", "-c", "echo 'Hello Kubernetes Storage!' > /data/test.txt && sleep 3600"] volumeMounts: - name: storage mountPath: /data volumes: - name: storage persistentVolumeClaim: claimName: my-pvc EOF

Step 4 — Verify data persists after Pod deletion

# Read the file written by the Pod kubectl exec storage-demo-pod -- cat /data/test.txt Hello Kubernetes Storage! # Delete the Pod — simulating a crash or rescheduling kubectl delete pod storage-demo-pod # Create a NEW Pod using the same PVC kubectl run verify-pod --image=busybox --restart=Never \ --overrides='{"spec":{"volumes":[{"name":"s","persistentVolumeClaim":{"claimName":"my-pvc"}}],"containers":[{"name":"c","image":"busybox","command":["cat","/data/test.txt"],"volumeMounts":[{"name":"s","mountPath":"/data"}]}]}}' kubectl logs verify-pod Hello Kubernetes Storage! # ← data survived Pod deletion ✅

Step 5 — Try dynamic provisioning with Minikube's default StorageClass

# Check Minikube's default StorageClass kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY standard (default) k8s.io/minikube-hostpath Delete # Create a PVC without specifying storageClassName — uses default kubectl apply -f - <<EOF apiVersion: v1 kind: PersistentVolumeClaim metadata: name: dynamic-pvc spec: accessModes: [ReadWriteOnce] resources: requests: storage: 1Gi EOF # PV was auto-created by StorageClass! kubectl get pvc dynamic-pvc NAME STATUS VOLUME CAPACITY dynamic-pvc Bound pvc-7f3a1b2c-auto-generated-name 1Gi ↑ PV created automatically — no manual PV needed ✅

Step 6 — Clean up

kubectl delete pod storage-demo-pod verify-pod kubectl delete pvc my-pvc dynamic-pvc kubectl delete pv my-pv

📊 PV vs PVC vs StorageClass — Quick Reference

FeaturePVPVCStorageClass
What it isActual storage resourceRequest for storageStorage type definition
Created byAdmin or StorageClassDeveloperAdmin (once)
ScopeCluster-wideNamespace-scopedCluster-wide
LifecycleIndependent of PodsTied to namespacePermanent definition
ProvisioningManual (static)Triggers dynamic provisionEnables dynamic provisioning
Used in Pod?✗ Not directly✓ Yes — via claimName✗ Not directly
apiVersionv1v1storage.k8s.io/v1

📌 Day 9 Recap — What You Learned

  • The problem: Pod filesystems are ephemeral — data is lost on Pod deletion or rescheduling. Databases and stateful apps need persistent storage that survives Pod lifecycle.
  • PV (Persistent Volume): The actual storage resource in the cluster. Cluster-scoped. Represents a real disk — EBS, GCP PD, NFS, or local. Created by admin or auto-provisioned.
  • PVC (Persistent Volume Claim): A storage request by a developer. Namespace-scoped. Specifies size and access mode. Kubernetes binds it to a matching PV automatically.
  • StorageClass: Defines the type of storage and enables dynamic provisioning. When a PVC references a StorageClass, a PV is auto-created — no admin action needed.
  • Static vs Dynamic: Static = admin creates PV manually. Dynamic = StorageClass creates PV automatically when PVC is submitted. Dynamic is the production standard.
  • Access modes: RWO (one node), ROX (many nodes read-only), RWX (many nodes read-write). Most block storage only supports RWO.
  • Reclaim policies: Retain (keep data), Delete (auto-delete disk), Recycle (deprecated). Delete is default for dynamic provisioning — be careful.
🚀 Day 10 preview: Now that you understand storage, the next step is StatefulSets — the right way to run databases and stateful apps in Kubernetes. StatefulSets combine stable Pod identities, ordered deployment, and persistent storage into one powerful workload type.

Comments

Popular posts from this blog

Linux Commands Every DevOps Engineer Must Know (2026 Complete Guide)

Docker Day 1: Containers Explained — VM vs Docker, Lifecycle & Buildah

Kubernetes Day 7: ConfigMaps & Secrets — Managing Config the Right Way