Persistent Volumes and Storage

intermediate kubernetes pv pvc storage-classes

Container storage is ephemeral by default. When a Pod dies, everything inside the container’s filesystem dies with it. That’s fine for stateless apps, but terrible for databases. If our PostgreSQL Pod restarts, we don’t want to lose all our data. That’s where Persistent Volumes come in.

The Three Pieces

Kubernetes storage has three main objects, and they work together like a request system.

Storage Flow
StorageClass
the "how" — defines provisioner
PVC (Claim)
the "request" — I need 10Gi
PV (Volume)
the actual disk/storage
Pod
mounts and uses it

PersistentVolume (PV) — the actual storage resource. Think of it like a physical hard drive in the cluster. It can be an AWS EBS volume, a GCP Persistent Disk, an NFS share, or local storage on a node.

PersistentVolumeClaim (PVC) — a request for storage. Our Pod says “I need 10Gi of fast storage” and Kubernetes finds (or creates) a matching PV.

StorageClass — defines how storage gets provisioned. Instead of manually creating PVs, the StorageClass tells Kubernetes to dynamically create them when a PVC asks.

Static Provisioning

We manually create a PV, then claim it with a PVC.

# The actual storage
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:                     # local storage (dev only!)
    path: /data/my-volume
---
# The request
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Dynamic Provisioning — The Better Way

In production, we don’t want to manually create PVs. We define a StorageClass and let Kubernetes provision storage on-demand.

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com   # AWS EBS CSI driver
parameters:
  type: gp3                     # SSD storage type
reclaimPolicy: Delete           # delete PV when PVC is deleted
volumeBindingMode: WaitForFirstConsumer

Now our PVC just references the StorageClass:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-storage
spec:
  storageClassName: fast-ssd    # use our StorageClass
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi

Kubernetes automatically creates a 20Gi EBS volume when this PVC is created. No manual PV needed.

Using a PVC in a Pod

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

Access Modes

  • ReadWriteOnce (RWO) — one node can mount it as read-write. Most common for databases.
  • ReadOnlyMany (ROX) — many nodes can mount it as read-only. Good for shared config or static assets.
  • ReadWriteMany (RWX) — many nodes can mount it as read-write. Needs special storage backends like NFS or EFS.

Most cloud block storage (EBS, Persistent Disk) only supports RWO. For RWX, we need network file systems.

Reclaim Policies

What happens to the PV when the PVC is deleted?

  • Delete — the PV and underlying storage are deleted. Default for dynamic provisioning.
  • Retain — the PV sticks around with its data. We have to manually clean it up. Safer for critical data.

Common Pattern: Database Storage

For databases, we typically use a StatefulSet with a volumeClaimTemplate. Each Pod gets its own PVC that persists across restarts.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 1
  template:
    spec:
      containers:
        - name: postgres
          image: postgres:16
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:          # each replica gets its own PVC
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 50Gi

In simple language, PVCs are like ordering storage from a menu (StorageClass). We say what we need, and Kubernetes provisions the real storage (PV) behind the scenes. Our Pod just mounts the PVC and uses it like a normal directory.