Bind a PersistentVolumeClaim
Containers are stateless by design. To keep data across restarts you
need a PersistentVolume (PV) โ a piece of real storage โ and a
PersistentVolumeClaim (PVC) โ a request for storage that pods
can mount.
In production a cluster admin pre-provisions PVs, or a dynamic
provisioner (the storage class) creates them on demand when a PVC
is submitted. This sandbox runs k3s, which ships a built-in
local-path provisioner. Confirm it:
kubectl get storageclass
local-path is marked (default).
Look at the PVC manifest:
cat storage/pvc-demo.yaml
It asks for 1Gi of ReadWriteOnce storage with no explicit
storageClassName, so it lands on local-path.
Apply it:
kubectl apply -f storage/pvc-demo.yaml
Watch the binding:
kubectl get pvc
STATUS flips from Pending to Bound once the provisioner has
created a PV and stitched them together. Look at the PV that just
appeared:
kubectl get pv
Click Verify step when the PVC is Bound.
Hint
`kubectl apply -f storage/pvc-demo.yaml` requests 1Gi of ReadWriteOnce storage; k3s's local-path provisioner auto-creates a matching PV.
Mount it in a pod and write to it
A PVC by itself is just an intent. To actually use the storage, a
pod has to mount it.
Look at the pod manifest:
cat storage/pod-volume-demo.yaml
The volumes: section references the PVC by name; the
volumeMounts: section attaches it inside the nginx container at
/var/www/html.
Apply:
kubectl apply -f storage/pod-volume-demo.yaml
Watch it become Ready (this also triggers the local-path provisioner
to finally create the PV):
kubectl get pvc,pod
Once pvc-demo-pod is Running, write a file into the mounted
volume:
kubectl exec pvc-demo-pod -- sh -c 'echo "Persistent storage works" > /var/www/html/index.html'
Read it back through nginx (the volume is mounted at the nginx
docroot):
kubectl exec pvc-demo-pod -- curl -s http://127.0.0.1/
You should see your message.
Click Verify step.
Hint
`kubectl apply -f storage/pod-volume-demo.yaml`, then `kubectl exec pvc-demo-pod -- sh -c 'echo hello > /var/www/html/index.html'`.
Verify the data survives pod deletion
The whole point of a PV: the data outlives any individual pod.
Delete the pod:
kubectl delete pod pvc-demo-pod
Wait for it to be gone:
kubectl get pods
The PVC, however, still exists and is still bound to the same PV:
kubectl get pvc,pv
Recreate the pod (same manifest, same PVC reference):
kubectl apply -f storage/pod-volume-demo.yaml
Wait for it to be Running:
kubectl wait --for=condition=Ready pod/pvc-demo-pod --timeout=60s
Read the file you wrote in step 2:
kubectl exec pvc-demo-pod -- cat /var/www/html/index.html
The same message comes back. The pod was destroyed; the data
wasn't.
Click Verify step.
Hint
Delete the pod, recreate it, then `kubectl exec` and `cat /var/www/html/index.html`. The file should still be there.
Move to a StatefulSet
A Deployment treats its pods as interchangeable cattle. For storage
systems โ databases, queues, file servers โ you usually need
stable identity: each replica should have a predictable name and
its own dedicated storage. That's a StatefulSet.
First, free up the PVC by removing the pod that owns it:
kubectl delete pod pvc-demo-pod
Now apply the StatefulSet:
cat storage/statefulset-demo.yaml
kubectl apply -f storage/statefulset-demo.yaml
Watch the pods come up:
kubectl get pods -l app=MyApp -w
(Ctrl-C to stop watching.)
Two things to notice:
- Sequential names:
statefulset-demo-0, statefulset-demo-1,
statefulset-demo-2. Not random hashes.
- One PVC per pod: from the
volumeClaimTemplates field.
kubectl get pvc
You'll see hello-web-disk-statefulset-demo-0,
hello-web-disk-statefulset-demo-1, hello-web-disk-statefulset-demo-2.
Click Verify step when all three StatefulSet pods are Running.
Hint
`kubectl apply -f storage/statefulset-demo.yaml` creates three ordered pods, each with its own PVC.
Stable pod names + per-pod identity
Each StatefulSet pod has its own PVC. Storage is not shared
across replicas โ each one is private.
Write a unique file into pod 0 only:
kubectl exec statefulset-demo-0 -- sh -c 'echo "hi from pod 0" > /var/www/html/index.html'
Read it back:
kubectl exec statefulset-demo-0 -- cat /var/www/html/index.html
Now look at pod 1 and pod 2 โ they should have empty docroots:
kubectl exec statefulset-demo-1 -- ls -la /var/www/html
kubectl exec statefulset-demo-2 -- ls -la /var/www/html
Each pod's /var/www/html is its own PVC's content, not pod 0's.
Test the rebuild guarantee. Delete pod 0:
kubectl delete pod statefulset-demo-0
The StatefulSet recreates a pod with the same name:
kubectl get pods -l app=MyApp
When statefulset-demo-0 is Running again, read the file:
kubectl exec statefulset-demo-0 -- cat /var/www/html/index.html
hi from pod 0 โ the new pod attached to the same PVC and the data
was waiting for it.
Real-world: this is how stateful databases survive node failures.
The replica that picks up db-0's identity (and PVC) thinks it
is db-0 and continues from its on-disk state.
Click Verify step.
Hint
`kubectl exec statefulset-demo-0 -- sh -c 'echo hi from 0 > /var/www/html/index.html'` โ the file is private to pod 0's PVC.