Lesson 07 of 32 · Level 1 — Foundations
Storage basics
Containers forget everything when they restart. Learn the volume types, how PersistentVolumeClaims and StorageClasses give pods durable disks, and prove your data survives a pod being deleted.
Containers forget
A container's filesystem is built fresh from the image every time it starts. Write a file, restart the container, and the file is gone. That's great for predictability and terrible for databases.
A container's disk is a whiteboard that gets wiped clean every morning. If you want to keep your homework, you write it in a notebook and put it in your locker. Kubernetes volumes are the notebooks and lockers:
- emptyDir is a notebook you share with your deskmate for the day. It's thrown away when the class ends (the pod is deleted).
- A PersistentVolumeClaim is your locker. You can leave, come back tomorrow (a new pod), and your stuff is still there.
The three pieces of persistent storage
| Piece | Who creates it | What it is |
|---|---|---|
| StorageClass | Cluster admin | A type of storage: "fast SSD", "cheap HDD", "shared NFS". |
| PersistentVolumeClaim (PVC) | You (the app) | A request: "I need 1 GiB, read-write". |
| PersistentVolume (PV) | Created automatically by the StorageClass's provisioner | The actual disk that satisfies the claim. |
You ask for storage with a PVC; Kubernetes finds or dynamically creates a PV that matches, and binds them together.
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer 1h
On kind, the default standard class creates folders on the node's disk. On EKS it would create EBS volumes; on OpenStack, Cinder volumes; on bare metal, Longhorn or Ceph. Your PVC YAML stays the same, and that's the point.
Prove data survives a pod
Create a claim:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: notes
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
# storageClassName omitted → the default class is used
$ kubectl apply -f pvc.yaml
persistentvolumeclaim/notes created
$ kubectl get pvc notes
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
notes Pending standard 4s
Pending is normal here. The class waits for a pod to use the claim (WaitForFirstConsumer) so the disk is created on the right node. Now a pod that writes to it:
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "date >> /data/log.txt; cat /data/log.txt; sleep 3600"]
volumeMounts:
- name: notes
mountPath: /data
volumes:
- name: notes
persistentVolumeClaim:
claimName: notes
$ kubectl apply -f writer.yaml
$ kubectl get pvc notes
NAME STATUS VOLUME CAPACITY ACCESS MODES
notes Bound pvc-3f1c2a4e-9d8b-4c1e-a7f2-0b6e5d4c3b2a 1Gi RWO
$ kubectl logs writer
Sat Sep 26 18:02:11 UTC 2026
$ kubectl delete pod writer
$ kubectl apply -f writer.yaml
$ kubectl logs writer
Sat Sep 26 18:02:11 UTC 2026
Sat Sep 26 18:04:37 UTC 2026
Two lines: the first pod's write survived the pod being deleted. 🎉
emptyDir: shared scratch space
emptyDir starts empty when the pod starts and is deleted with the pod. It survives container restarts, and it's how two containers in the same pod share files:
spec:
containers:
- name: producer
image: busybox:1.36
command: ["sh", "-c", "while true; do date > /shared/now.txt; sleep 5; done"]
volumeMounts: [{ name: shared, mountPath: /shared }]
- name: consumer
image: busybox:1.36
command: ["sh", "-c", "while true; do cat /shared/now.txt; sleep 5; done"]
volumeMounts: [{ name: shared, mountPath: /shared }]
volumes:
- name: shared
emptyDir: {}
Be careful with hostPath
hostPath mounts a directory from the node itself. It ties the pod to one node and can expose the node's filesystem (a security risk). It's used by system agents (log collectors, CNI), not by normal apps.
Try it: find your data on the node
- Run
kubectl get pvandkubectl describe pv <name>. Look for the node affinity and the path on the node. kubectl get pod writer -o wideshows which node it's on. Open a shell there:docker exec -it lab-worker bash(orlab-worker2).ls /var/local-path-provisioner/, thencatthelog.txtinside. That's the "locker", physically.- Delete the PVC (
kubectl delete pod writer; kubectl delete pvc notes) and check the folder again. WithreclaimPolicy: Delete, it's gone.
Going deeper: what bites in production
- RWO is per node, not per pod. Two pods on the same node can mount the same RWO volume. Need strict single-writer? Use
ReadWriteOncePod. - Zones: cloud block disks (EBS) live in one availability zone, and a pod can't mount a disk from another zone. That's why
WaitForFirstConsumermatters. - Node failure with RWO: the replacement pod may sit in
ContainerCreatingwith a Multi-Attach error until the old attachment is released. Know how your CSI driver handles this before you need it. - Use
Retainfor data you can't afford to lose, and back up with VolumeSnapshots and Velero. A reclaim policy is not a backup. - For databases, use a StatefulSet with
volumeClaimTemplates: each replica gets its own PVC (data-db-0,data-db-1). More in Kubernetes Storage & Data Protection.
Recap
- Container filesystems are ephemeral; use volumes for anything worth keeping.
- emptyDir = pod-lifetime scratch space; PVC = durable storage that outlives pods.
- StorageClass (type) → PVC (request) → PV (actual disk), provisioned dynamically.
PendingwithWaitForFirstConsumeris normal; RWO means one node; reclaim policyDeletedeletes your data.
This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.