Kubernetes Persistent Storage: PV, PVC, and StorageClass Explained

August 17, 2026 · 8 min read

Your database pod restarts. The data is gone. You scale a StatefulSet down and the volume vanishes with the pod. This is the moment every DevOps engineer discovers the hard truth about Kubernetes: container filesystems are ephemeral. When a pod dies, its writable layer dies with it.

For stateless services that's fine — that's what ReplicaSets are for. But databases, message queues, caches, and file uploads need storage that survives pod restarts, rescheduling, and node failures. That's what PersistentVolumes, PersistentVolumeClaims, and StorageClasses are for. Here's how they work together, with YAML you can actually use.

1. The Three-Layer Model

Kubernetes separates what you need from what's available:

Think of PV as the physical disk, PVC as the lease agreement, and StorageClass as the disk factory.

2. Static Provisioning: Admin Creates the Volume

The old-school way. An admin provisions a PV pointing at real storage:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: postgres-data-pv
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data/postgres

Then an app claims it:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: manual

A claim binds to a PV when the requested size, access mode, and StorageClass all match. Once bound, the pod mounts it:

spec:
  containers:
    - name: postgres
      image: postgres:16
      volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: postgres-data

Static provisioning works, but it's tedious — every volume is hand-made, sized in advance, and usually wasted or undersized. In production you almost never do this. You use dynamic provisioning.

3. Dynamic Provisioning: StorageClass Does the Work

A StorageClass tells Kubernetes how to create volumes on demand. On AWS it looks like this:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "3000"
  encrypted: "true"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

Now your claim just references the class and Kubernetes provisions the volume automatically — no admin involved:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 10Gi

kubectl get pvc shows the claim go from Pending to Bound in seconds. The volume is created, attached to a node, and mounted — all without a human touching the cloud console.

4. Access Modes: One Writer or Many?

The accessModes field decides who can mount the volume and how:

This is the #1 source of storage surprises. You deploy a multi-replica app with an RWO volume and half your replicas sit in ContainerCreating. They can't mount because the volume is already attached to another node. If you need multiple replicas sharing storage, you need a RWX-capable class, not block storage.

5. StatefulSets: Storage as Part of the Pod's Identity

StatefulSets pair each replica with its own volume using volumeClaimTemplates. When the pod is rescheduled, it reconnects to its volume — this is what makes stateful workloads (PostgreSQL, Cassandra, Kafka) safe to run on Kubernetes:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mongo
spec:
  serviceName: mongo
  replicas: 3
  selector:
    matchLabels:
      app: mongo
  template:
    metadata:
      labels:
        app: mongo
    spec:
      containers:
        - name: mongo
          image: mongo:7
          volumeMounts:
            - name: data
              mountPath: /data/db
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi

Each replica gets a volume named data-mongo-0, data-mongo-1, data-mongo-2. Kill pod 1, it comes back on any node and reattaches to data-mongo-1. The data follows the identity, not the node.

6. Reclaim Policies: What Happens When You Delete the Claim?

Every PV has a persistentVolumeReclaimPolicy, set by its StorageClass:

Never delete a PVC holding production data without a verified backup. "It's just a claim, I'll recreate it" is how databases get lost.

7. Production Pitfalls

Summary

Storage is where Kubernetes stops being a convenient orchestration layer and starts being a stateful platform. Get the PV/PVC/StorageClass model right and your databases run as smoothly as your stateless APIs — and survive a node reboot without losing a byte.