1. PersistentVolumes & Claims
Kubernetes splits storage into two objects, deliberately separating "what storage exists" from "what a Pod asked for" — the same split you'll recognize from real infrastructure procurement.
PersistentVolume (PV) # a piece of ACTUAL storage in the cluster — could be
# a cloud disk, NFS share, or local disk. Cluster-scoped,
# created by an admin or dynamically by a StorageClass.
PersistentVolumeClaim (PVC) # a Pod's REQUEST for storage — "I need 10Gi, ReadWriteOnce."
# Kubernetes finds (or provisions) a PV that satisfies it
# and BINDS the two together.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
spec:
containers:
- name: postgres
image: postgres:16
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: pg-storage
The Pod spec never mentions cloud disks, NFS servers, or physical paths — it just names a claim. That indirection is exactly what lets the same manifest work unchanged on a local cluster, AWS, GCP or on-prem, differing only in how the PV behind the claim gets provisioned.
A Docker named volume solved "survive a container being recreated on the same host." A PVC solves the harder version: "survive a Pod being recreated on possibly a DIFFERENT node." The concept — name the storage, mount it at the right path — carries over directly.
2. StorageClasses & Dynamic Provisioning
Manually pre-creating a PV for every PVC doesn't scale. A StorageClass tells Kubernetes how to provision storage automatically the moment a PVC asks for it — no admin has to create a PV by hand first.
kubectl get storageclass
# NAME PROVISIONER
# standard (default) k8s.io/minikube-hostpath
# On a cloud cluster you'd typically see something like:
# gp3 (default) ebs.csi.aws.com
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-storage
spec:
storageClassName: standard # <- triggers automatic PV creation
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
Apply that PVC and, within seconds, a matching PV appears automatically — created by the StorageClass's provisioner, sized exactly to the request, bound to your claim. Most managed cloud clusters ship with a sensible default StorageClass, which is why you often don't even need to specify one explicitly.
If a new PV doesn't appear and your PVC stays "Pending," check the StorageClass name for typos, or confirm your cluster actually has a provisioner configured — a common local-cluster gotcha, since not every local setup ships dynamic provisioning out of the box.
3. Access Modes & Reclaim Policy
Two settings shape how a PV can be used and what happens to it after its claim is deleted — both easy to get wrong the first time.
ReadWriteOnce (RWO) # mountable read-write by a SINGLE node at a time. Most common —
# fits a database with one Pod writing to it.
ReadOnlyMany (ROX) # mountable read-only by MANY nodes simultaneously. Good for
# shared, static reference data.
ReadWriteMany (RWX) # mountable read-write by MANY nodes simultaneously. Needs a
# storage backend that supports it (NFS, some cloud file
# systems) — most cloud block storage (EBS, etc.) is RWO only.
Retain # the PV and its data survive PVC deletion, but move to "Released" status —
# NOT automatically reusable until an admin manually intervenes. Safest default
# for anything you can't afford to lose.
Delete # the underlying storage is deleted along with the PVC. Common default for
# dynamically-provisioned dev/test volumes — convenient, but genuinely
# destructive if applied to production data by mistake.
Many dynamically-provisioned StorageClasses default to Delete — meaning an accidental kubectl delete pvc on production data is unrecoverable through Kubernetes alone. For anything that matters, explicitly set reclaimPolicy: Retain, or rely on a separate backup strategy (the same lesson from Week 3's volume backups).
4. A Database on Kubernetes
Putting it together: PostgreSQL, on Kubernetes, with dynamically-provisioned storage that survives the Pod being deleted and rescheduled.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pg-storage
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASSWORD
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: pg-storage
Note replicas: 1 — a plain Deployment with a shared PVC doesn't safely
support multiple replicas writing to the same ReadWriteOnce volume simultaneously.
Running a genuinely multi-replica, stateful database correctly is exactly what
StatefulSets (Week 15) exist for; a single-replica Deployment is a reasonable,
simpler starting point for one database instance.
Even with all these tools, a large number of real production setups use a managed database service (RDS, Cloud SQL) rather than self-hosting a database on Kubernetes — stateful data adds real operational weight. Knowing how to do it yourself is valuable regardless of which choice you make.
5. Hands-on Exercise
Run a database on Kubernetes and prove its data survives Pod deletion
Set up a stateful workload with a PVC, populate it with real data, then delete and recreate the Pod to confirm the storage abstraction is doing its job.
Part 1 — Set it up:
- Write a PVC requesting storage from your cluster's default StorageClass, and a single-replica Deployment for Postgres (or MySQL/MongoDB) mounting it at the correct data path.
- Apply both, and confirm with
kubectl get pvcthat it's Bound, andkubectl get pvthat a matching PV was dynamically created. - Exec into the running Pod, connect to the database, and create a table with a few rows.
If your PVC stays in Pending status, run kubectl describe pvc <name> and check the Events at the bottom — it'll usually tell you exactly why (no matching StorageClass, no provisioner configured, etc.).
Part 2 — Prove the persistence, the hard way:
- Delete the Pod directly (not the Deployment) —
kubectl delete pod <pod-name>— and wait for the Deployment controller to create a replacement. - Connect to the new Pod and confirm your table and data are still there.
- Now delete the entire Deployment (
kubectl delete deployment postgres, leaving the PVC alone) and recreate it fresh from your manifest. Confirm the data survives this too. - Finally, delete the PVC itself and check
kubectl get pv— is the underlying PV gone, or does it still exist in a "Released" state? Explain what this tells you about your StorageClass's reclaim policy, based on what you observe.
If you're on Minikube's default hostpath provisioner, its default reclaim policy is typically Delete — a good, safe environment to actually witness data loss on purpose, which is a much better place to learn this lesson than on a real production cluster.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What's the relationship between a PersistentVolume and a PersistentVolumeClaim?
What's the relationship between a PersistentVolume and a PersistentVolumeClaim?
A PersistentVolume represents actual provisioned storage in the cluster. A PersistentVolumeClaim is a Pod's request for storage meeting certain criteria (size, access mode). Kubernetes binds a matching PV to a PVC, and the Pod mounts the claim — never referencing the underlying storage directly.
Q2
What does a StorageClass let you avoid doing manually?
What does a StorageClass let you avoid doing manually?
Pre-creating a PersistentVolume by hand for every claim. A StorageClass defines HOW to dynamically provision storage on demand, so a PV is created automatically the moment a matching PVC is submitted, sized exactly to what was requested.
Q3
Why can't you safely run multiple replicas of a Deployment all writing to the same ReadWriteOnce PVC?
Why can't you safely run multiple replicas of a Deployment all writing to the same ReadWriteOnce PVC?
ReadWriteOnce means the volume can be mounted read-write by only a single node at a time — multiple Pods (likely on different nodes) trying to write to it concurrently isn't safely supported. A single-replica Deployment sidesteps this; genuinely replicated stateful workloads need StatefulSets with per-replica storage instead.
Q4
What's the practical risk of a StorageClass with reclaimPolicy: Delete on production data?
What's the practical risk of a StorageClass with reclaimPolicy: Delete on production data?
Deleting the PVC (accidentally or otherwise) permanently deletes the underlying storage and all its data along with it, with no recovery path through Kubernetes. For anything you can't afford to lose, use reclaimPolicy: Retain or maintain a separate, independent backup strategy.