§8 persistent volume
Source: examples/08-persistent-volume/ · ← Back to examples index
Demonstrates Kubernetes PersistentVolumes (PVs) and
PersistentVolumeClaims (PVCs) by deploying nginx with content
served from a PV instead of baked into the image. The demo
includes a real persistence test: capture content from
http://127.0.0.1:18080/, delete the Pod, poll the same URL until the
replacement Pod answers, capture again — timestamps match → PV did its job.
The example inverts the §6 pattern. §6 baked content into the image. §8 uses a generic image; content lives in a PV and outlives any Pod that mounts it.
What it tests
Seven §8 claims:
- minikube’s
default-storageclass+storage-provisioneraddons provide a workingstandardStorageClass withk8s.io/minikube-hostpathas provisioner - A PVC without
storageClassNameis bound to a dynamically provisioned PV from the default class - An
initContainercan seed a fresh PV with content before the main container starts - Mounting the PVC at
/usr/share/nginx/htmloverlays the image’s baked-in content (so the same image can serve different content via volume mount) - The Deployment reaches
Availablewith the PVC mounted - Deleting the Pod triggers a replacement that re-mounts the same PVC
- The replacement Pod sees the same content the original wrote — timestamps match → PV is independent of Pod lifecycle
Running
./demo.sh
Expected duration: 30-50 seconds. Most of it is waiting for the Pod restart cycle in the persistence test (~15-25 seconds for the new Pod to come up after deletion).
If nginx-custom:v1 isn’t loaded, add 2-4 minutes for the first
build (docker build from §6’s Containerfile, then minikube image
load). The initContainer uses the pinned
ubi10/ubi-minimal:10.2-1791444377 image.
What you should see
==> step lines for each phase. Highlights:
==> capturing initial content from PV
initial timestamp from PV: 2026-05-17T04:12:33Z
✓ content seeded by initContainer, served by nginx
==> deleting Pod to test persistence across Pod lifecycle
old Pod: nginx-pv-abc123-x7y8z
waiting for replacement Pod (up to 90s)
✓ Pod replaced; Deployment Available again
==> checking new Pod's initContainer log (should report existing content)
[seed-content] content already exists; persistence is working
✓ initContainer found existing content → PV persisted
==> verifying content persisted across Pod restart
before Pod restart: 2026-05-17T04:12:33Z
after Pod restart: 2026-05-17T04:12:33Z
✓ timestamps match — PV did its job across Pod lifecycle
==> SUCCESS — Deployment + PVC + persistence all verified
Cluster scope
Uses the default minikube cluster and pins every kubectl call to
that context. The nginx-pv resources (Deployment, Service, PVC) all
use distinct names from §6 and §7 (nginx, nginx-np). The Service is
type: NodePort on 30080, published at profile creation as
127.0.0.1:18080. That slot is shared with §6, §9 and §12-http, one at
a time: the demo calls require_free_nodeport 30080 first. The PV the StorageClass
provisions is named like pvc-<uuid> and is also distinct from
anything §6/§7 creates.
Cleanup
demo.sh installs a trap cleanup EXIT that:
- Deletes the manifests, which cascades to:
- Deployment → Pods terminated
- Service deleted (frees the shared nodePort 30080)
- PVC deleted → PV auto-deleted (Delete reclaim policy)
- The minikube node’s hostpath directory under
/tmp/hostpath-provisioner/is cleaned by the provisioner
nginx-custom:v1 stays in the cluster’s image cache.
Manual cleanup if needed:
kubectl --context minikube delete -f manifests/ --ignore-not-found=true
# the PV is auto-deleted; verify with:
kubectl get pv
When this fails
- PVC stays
Pendingforever — the storage-provisioner addon is disabled.minikube addons listto confirm;minikube addons enable storage-provisioner default-storageclassto fix - Deployment Pending:
pod has unbound immediate PersistentVolumeClaims— same root cause as above. The PVC isn’t binding because there’s no provisioner - initContainer crashes — typically a sh syntax issue (the heredoc), or permission denied writing to /content. The demo’s failure path dumps the initContainer log
- Timestamps don’t match — the PV genuinely didn’t persist. Most likely: the PVC was deleted between Pod lifecycles (the demo’s cleanup runs on script exit, not between phases — so this shouldn’t happen in the demo, but might if you’ve been experimenting manually)
- The NodePort never answers after the Pod is replaced — the
new endpoint needs a few seconds to register. The demo polls for
30s; longer than that suggests a Service selector mismatch
(
kubectl --context minikube get endpoints nginx-pv) nodePort 30080 is held by Service ...— §6, §9 or §12-http holds the shared slot. Run thekubectl delete svccommand the script printsdoes not publish nodePort 30080— the profile was created without the port map. Run the delete-and-recreate command the script prints
For any of these, paste the failing output back.