NovuSpark
All articles

April 17, 2026 · NovuSpark Team

Kubernetes Storage: Volumes, PVCs, and StorageClasses

This is the fourth post in our Kubernetes fundamentals series, building on Pods and Deployments and ConfigMaps and Secrets.

We covered this exact problem for plain Docker containers earlier in this blog: a container's own writable filesystem layer disappears the moment the container is removed. Kubernetes inherits that same disposability at the Pod level — a replaced Pod gets a brand new, empty filesystem — and adds its own set of objects for solving it properly at cluster scale.

The simplest case: emptyDir

apiVersion: v1
kind: Pod
metadata:
  name: cache-pod
spec:
  containers:
    - name: app
      image: my-app:1.0
      volumeMounts:
        - name: cache-volume
          mountPath: /tmp/cache
  volumes:
    - name: cache-volume
      emptyDir: {}

An emptyDir volume is created empty when the Pod starts and shares that storage across every container in the Pod — genuinely useful for temporary scratch space or for one container in a Pod to pass data to another. It is not a durable storage solution: it's deleted the moment the Pod itself is removed, exactly like the underlying container filesystem it's meant to improve on. If a Pod restarts (rather than being removed and recreated), the emptyDir typically does survive — but that distinction is fragile enough that it shouldn't be relied on for anything that actually matters.

PersistentVolumes and PersistentVolumeClaims: storage that outlives a Pod

For real durability, Kubernetes separates the request for storage from the actual provisioning of it — two objects, working together:

# PersistentVolumeClaim (PVC) — the request
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: postgres
          image: postgres:16
          volumeMounts:
            - name: db-storage
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: db-storage
          persistentVolumeClaim:
            claimName: db-data

A PersistentVolumeClaim (PVC) is a request: "I need 10Gi of storage, readable and writable by one Pod at a time." A PersistentVolume (PV) is the actual underlying storage that gets bound to satisfy that request — an AWS EBS volume, a GCP persistent disk, an NFS share, depending on the cluster's environment. Crucially, the PVC — and the real data behind it — survives the Pod being deleted and recreated. A new Pod referencing the same PVC reattaches to the exact same underlying storage, data intact.

Why database Pods use StatefulSets, not Deployments

Notice the example above uses a StatefulSet, not a Deployment — this is the detail most people miss the first time they try to run a database in Kubernetes. A Deployment's replicas are meant to be interchangeable — any replica can be replaced by any other, with no individual identity. A database replica very much has an identity: it has specific data attached to it, and swapping one for an identically-configured but empty replacement is not the same operation at all.

A StatefulSet gives each replica a stable, unique identity (db-0, db-1, not an arbitrary generated suffix) and — this is the important part — its own dedicated PVC, created automatically per replica and reattached to that same specific replica if it's ever recreated. This is why stateful workloads (databases, message queues, anything where individual replica identity and attached storage genuinely matter) use StatefulSets, while stateless application servers use Deployments.

StorageClasses: how the actual provisioning happens

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  iops: "4000"
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
spec:
  storageClassName: fast-ssd
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

A StorageClass tells Kubernetes how to dynamically provision storage when a PVC requests it — which cloud disk type, which performance tier — rather than requiring a cluster administrator to manually pre-create PersistentVolumes for every possible request in advance. Referencing storageClassName: fast-ssd in a PVC triggers automatic provisioning of exactly the kind of underlying disk that StorageClass describes, the moment the PVC is created.

reclaimPolicy deserves specific attention: Delete (the common default) means the underlying cloud disk is destroyed when its PVC is deleted — appropriate for genuinely disposable data, actively dangerous for anything that isn't. Retain keeps the underlying storage even after the PVC referencing it is deleted, at the cost of needing manual cleanup later. Getting this backwards in either direction is a real, recoverable-only-with-a-backup mistake — worth checking deliberately rather than assuming the default matches your intent.

What to actually remember from this post

  • emptyDir is temporary scratch space, tied to the Pod's lifetime — not a durable storage solution.
  • PVCs are requests; PVs are the actual provisioned storage — a PVC and its underlying data survive a Pod being deleted and recreated.
  • StatefulSets, not Deployments, are for workloads with real per-replica identity and attached storage — most obviously, databases.
  • reclaimPolicy on a StorageClass decides whether deleting a PVC destroys the underlying disk — verify it matches what you actually intend for genuinely important data.

Next in the series: Autoscaling and Resource Management in Kubernetes, the final post — covering how Kubernetes decides how much CPU and memory a Pod actually gets, and when to add more of them.

Ready when you are

Want training built around your team's real work?

Tell us about your team and what you're trying to solve — we'll recommend a program that fits.