Storage: Volumes, PV, PVC and StorageClass
So far our application is stateless — a pod dies, a new pod is built, nothing is lost. But what about a database, files users upload, or data that needs to be kept? Anything written inside the pod will evaporate with the pod when it dies. This article solves that: how to store data that outlives the pod's lifecycle.
The problem: the pod's filesystem is ephemeral
By default, the container filesystem is tied to the pod's lifecycle. A pod gets deleted or rebuilt (remember self-healing, rolling update in Article 4) → everything written inside disappears. For a stateless app that's fine; for data that must be kept it's a disaster. Kubernetes has volumes to attach a storage area to a pod — and crucially, the durable kind of volume.
The simplest volume is
emptyDir: an empty directory that lives with the pod, used to share files between containers within the same pod or as a temporary cache. ButemptyDiris also lost when the pod dies — not what we need for durable data. What we need is a PersistentVolume.
Three concepts: PV, PVC, StorageClass
Kubernetes separates the demand for storage from the supply of storage — like booking a hotel room without needing to know which specific room:
PVC (PersistentVolumeClaim) "I need 100Mi, read-write" ← you (dev) declare
│ Kubernetes matches
▼
PV (PersistentVolume) real 100Mi disk ← infrastructure provides
│ provisioned by
▼
StorageClass "how to provision disk" (disk type) ← admin defines
- PersistentVolume (PV) — a real piece of storage in the cluster (an EBS disk, a directory on the host, an NFS share...). It's a cluster-scoped resource.
- PersistentVolumeClaim (PVC) — your storage request: "need 100Mi, ReadWriteOnce access mode". You work with the PVC, not the PV directly.
- StorageClass — defines how to provision PVs dynamically. When you create a PVC, the StorageClass automatically creates a matching PV — called dynamic provisioning. No need to create PVs by hand.
This separation matters: the dev only says "I need this much capacity"; how that disk comes to exist (EBS on AWS, hostpath on minikube...) is the StorageClass's job. The same PVC runs on any infrastructure.
minikube ships with a default StorageClass
kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE AGE
standard (default) k8s.io/minikube-hostpath Delete Immediate 14m
minikube provides standard (marked default), using minikube-hostpath — stored on the node's disk. Because there's a default StorageClass, we only need to create a PVC and a PV appears automatically.
Create a PVC and watch it get a PV
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Mi
kubectl apply -f pvc.yaml
kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
persistentvolumeclaim/data-pvc Bound pvc-04182fed... 100Mi RWO standard
NAME CAPACITY RECLAIM POLICY STATUS CLAIM STORAGECLASS
persistentvolume/pvc-04182fed... 100Mi Delete Bound default/data-pvc standard
The magic of dynamic provisioning: we only created a PVC, but a PV (pvc-04182fed...) appears on its own and both are in the Bound state (paired up). The StorageClass quietly provisioned the disk. ACCESS MODES: RWO is ReadWriteOnce — one node attaches read-write at a time (enough for most cases; for many nodes writing at once there's ReadWriteMany, which needs a backend that supports it).
Experiment: data outlives the pod
This is the proof. Attach the PVC to a pod, write data, delete the pod, build a new pod reusing that PVC, then read it back.
# excerpt of the pod spec
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc
kubectl apply -f pod-writer.yaml
# write one line into the volume
kubectl exec writer -- sh -c 'echo "data outlives the pod - $(date)" > /data/note.txt; cat /data/note.txt'
data outlives the pod - Sat May 23 11:16:26 UTC 2026
Now delete the pod then build a new pod (same PVC):
kubectl delete pod writer
kubectl apply -f pod-writer.yaml
kubectl exec writer -- cat /data/note.txt
data outlives the pod - Sat May 23 11:16:26 UTC 2026
The exact same line, intact. The pod died and was reborn completely new, but the data in the PVC stayed put. This is exactly what emptyDir or the pod filesystem cannot do. Databases, uploads, logs that need to be kept — all rely on this mechanism.
reclaim policy: what happens when you delete the PVC
The RECLAIM POLICY: Delete column above means: when you delete the PVC, the PV (and its data) is deleted too. Suitable for dev/test. Production usually sets Retain so the PV (and data) is kept when the PVC is gone, avoiding accidentally deleting precious data. This is a knob you need to be aware of before deleting a PVC in a real environment.
Wrap-up
The pod filesystem is ephemeral — lost when the pod dies, so data that needs keeping must live outside the pod. Kubernetes separates the PVC (the demand: "need this much capacity") from the PV (the real disk), with StorageClass provisioning PVs dynamically (dynamic provisioning) — the dev only works with the PVC, and it runs on any infrastructure. Create a PVC and you automatically get a Bound PV; attach it to a pod via persistentVolumeClaim. The delete-then-recreate experiment proves data outlives the pod. Mind the reclaim policy (Delete vs Retain) so you don't delete data by accident. (StatefulSet in Article 12 builds on this exact mechanism for stateful apps.)
The application now has configuration and storage. There's one piece left to serve real users: getting HTTP from the outside in, routing by domain/path. Article 9: Ingress.