Chapter 10

Persist Data with a PVC

Store messages in SQLite on a PersistentVolumeClaim, verify restart survival, and understand stateful scaling tradeoffs.

Just started Saved in this browser.

Pods are replaceable. A PersistentVolumeClaim (PVC) asks Kubernetes for storage whose lifecycle is separate from a particular Pod. The persistent snapshot mounts one claim at /data, where FastAPI stores messages.db.

A FastAPI StatefulSet mounts a claim backed by Minikube storage

Step by step

Download this chapter's files

Use your browser to download either ivia-chapter-10.tar.gz or ivia-chapter-10.zip. The archive is self-contained; you do not need this tutorial repository.

Open a terminal after the browser download:

cd ~/Downloads
tar -xzf ivia-chapter-10.tar.gz
cd ivia-chapter-10

All remaining relative paths in this chapter start from that extracted directory.

Do this

The challenge

Challenge: create a message, delete the backend Pod, and prove the replacement Pod reads the same message from persistent storage.

Prepare

Prerequisites

  • A running Minikube cluster with the ingress addon available.
  • Helm, kubectl, and the Minikube node label.
  • Test host resolution or willingness to use curl --resolve.
  • About 1 GiB of provisionable Minikube storage.
Outcome

Learning goals

  • Create a chart-managed PVC
  • Mount the PVC into a StatefulSet
  • Verify data survival across Pod replacement
  • Explain why this SQLite design deliberately uses one replica
Step by step

Build persistent images

minikube addons enable ingress
kubectl label node minikube ivia.ch/nodetype=app --overwrite
minikube image build -t simple-backend:persistent persistent-app/backend
minikube image build -t simple-frontend:persistent persistent-app/frontend

Inspect the important values:

grep -n -E 'type:|replicaCount:|pvcs:|mountPath:|storage:|storageClassName:' \
  persistent-app/values.yaml

The backend is a one-replica StatefulSet. Its unnamed claim becomes simple-app-backend-0, requests 1Gi, uses ReadWriteOnce, and mounts at /data.

Step by step

Deploy and wait for storage

helm upgrade --install simple-app \
  oci://harbor.ivia.ch/ivia-generic-helm-chart/ivia-generic-helm-chart \
  --version 4.8.0 -n tutorial --atomic --timeout 5m \
  -f persistent-app/values.yaml

kubectl get statefulset,pods,pvc -n tutorial
kubectl wait --for=condition=Ready pod \
  -l app.kubernetes.io/component=backend \
  -n tutorial --timeout=180s
Step by step

Write, replace, and read

MINIKUBE_IP=$(minikube ip)
curl --fail --resolve api.simple-app.test:80:$MINIKUBE_IP \
  -H 'Origin: http://simple-app.test' \
  -H 'Content-Type: application/json' \
  -d '{"text":"I survived a Pod restart!"}' \
  http://api.simple-app.test/api/messages
curl --fail --resolve api.simple-app.test:80:$MINIKUBE_IP \
  http://api.simple-app.test/api/messages

Delete only the Pod, not the claim:

BACKEND_POD=$(kubectl get pod -n tutorial \
  -l app.kubernetes.io/component=backend \
  -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod "$BACKEND_POD" -n tutorial
kubectl wait --for=condition=Ready pod \
  -l app.kubernetes.io/component=backend \
  -n tutorial --timeout=180s
curl --fail --resolve api.simple-app.test:80:$MINIKUBE_IP \
  http://api.simple-app.test/api/messages
Step by step

Stateful tradeoffs

This KISS design is educational, not horizontally scalable. A ReadWriteOnce claim is normally mounted read-write by one node, and SQLite expects one shared local file. Two backend replicas could contend for the file or be unable to mount it as intended. For multiple replicas, use a networked database such as PostgreSQL, add migrations and backups, and keep API Pods stateless.

A StatefulSet gives stable Pod identity and ordered replacement; the PVC gives durable storage. Neither automatically provides backups, replication, encryption, or disaster recovery.

Verify

Expected validation

The PVC shows Bound; the backend Pod is 1/1 Running; both list requests contain I survived a Pod restart!; and the replacement Pod has a newer age than the PVC. The browser message board should show the same entry.

Troubleshoot

Troubleshooting and common pitfalls

  • PVC stays Pending: run kubectl get storageclass and kubectl describe pvc simple-app-backend-0 -n tutorial; Minikube normally provides standard.
  • Permission denied at /data: keep the image's /data ownership and non-root user setup intact; inspect backend logs.
  • Deleting the namespace or PVC: that can delete stored data. The challenge deletes only the Pod.
  • Scaling to two replicas: do not scale this SQLite stage; migrate durable state to a multi-client database first.
  • Health endpoint returns 503: it tests SQLite access; inspect mount events, filesystem permission, and logs.
  • Assuming PVC equals backup: take an independent backup before upgrades or destructive tests.

Your message outlived its process. You have now experienced the key boundary between replaceable compute and durable state.

Reference

Official references

Just started Saved in this browser.