Understand PVCs for Python Data
Understand PVCs for Python app data — Kubernetes for Python Developers tutorial.
Focus: understand pvcs for python app data
You've built a Python API that serves user uploads, or a Flask app backed by SQLite. It works perfectly in local development — until you redeploy the pod and every file, every database row, vanishes. That's the harsh reality of Kubernetes pods: they are ephemeral by design. Persistent Volume Claims (PVCs) are how you tell Kubernetes, "hey, my Python app's data needs to outlive this pod." In this lesson, you'll understand PVCs for Python app data, apply that knowledge hands-on, and connect it to the next logical step in your Kubernetes journey.
The problem this lesson solves
Kubernetes pods are ephemeral. They can be killed, rescheduled, or replaced at any moment — for a failed health check, a node reboot, or a rolling deployment. Any data written to the pod's container filesystem lives only as long as the pod itself. When the pod dies, so does your data.
Think about what that means for Python apps:
- A SQLite database file (e.g.,
app.db) stored inside the container is wiped clean on every restart. - User-uploaded files saved to
./uploadsdisappear without a trace. - Log files or generated reports are lost forever.
This is not just an inconvenience. In production, losing data is a business-critical failure. If your Python service is stateless — like a simple REST API that reads from an external database — you might get away with it. But the moment your app stores any state locally, you need persistent storage.
The pain: You have a working Python app containerized and running in Kubernetes, but every pod restart resets your application to a blank slate. You need a way to make storage survive pod lifecycles.
Core concept / mental model
To understand PVCs, think of a storage request — similar to how a Pod requests CPU or memory. A PersistentVolumeClaim (PVC) is an abstraction that requests storage from the cluster. It's not the actual storage itself; it's a claim on storage.
Here's a simple analogy: imagine a shared office printer. The printer is the PersistentVolume (PV) — the actual physical resource. When you submit a print job, you use a claim that reserves the printer for your task. That claim is your PVC. The system decides which printer to assign based on your requirements (size, speed, etc.). After you're done, you release it.
In Kubernetes:
- PersistentVolume (PV) — the actual storage resource (a NFS share, an EBS volume, a local disk). It's cluster-wide and independent of any pod.
- PersistentVolumeClaim (PVC) — a request for storage with specific characteristics (size, access mode, storage class). When you create a PVC, Kubernetes tries to bind it to a PV that satisfies the request.
- Pod — the running instance of your Python app. It uses the PVC by attaching it as a volume mount.
It's important to understand the request analogy because your Python app doesn't interact with the physical storage directly. It sees only the file system path. That path points to the PVC, which in turn maps to a PV — possibly a network disk shared across nodes. The pod doesn't care, and neither should your Python code.
Key definitions
- PersistentVolume (PV): A storage resource in the cluster, provisioned by an admin or dynamically by a storage class.
- PersistentVolumeClaim (PVC): A namespace-scoped request for storage that binds to a PV. It's what you, the developer, typically create.
- Access Modes: Define how the volume can be mounted:
ReadWriteOnce(single node),ReadOnlyMany(many nodes, read-only),ReadWriteMany(many nodes, read-write). - Storage Class: Defines the type of storage (e.g.,
standard,fast-ssd) and enables dynamic provisioning.
How it works step by step
When you create a PVC in Kubernetes, the following sequence happens behind the scenes:
- PVC creation — You submit a manifest that describes your storage request: 1 GiB,
ReadWriteOnce, storage classstandard. - Matching — Kubernetes looks for an existing PV that satisfies the request (size >= requested, access mode matches, storage class matches). If none exists and dynamic provisioning is enabled, it creates a PV for you.
- Binding — The PVC is bound to a specific PV. They are 1:1; a PVC can only bind to one PV, and a bound PV is dedicated to that claim.
- Pod usage — You reference the PVC in a Pod spec via a
volumeand mount it at a path in the container (e.g.,/app/data). - Pod lifecycle — When the pod is deleted, the PVC and its data remain. When a new pod is created referencing the same PVC, it gets the same data.
This is the critical benefit: PVCs decouple storage from the pod lifecycle. Your Python app can restart, scale, or be rescheduled — the data stays intact.
Lifecycle phases
A PVC goes through phases: Pending (waiting for PV), Bound (matched), and Released (when the claim is deleted). In Pending, the PVC is not usable — your pod will hang if it references it.
Hands-on walkthrough
Let's put this into practice. We'll create a PVC, mount it into a Python app pod, and verify that data persists across pod restarts.
1. Write a Python app that writes to disk
Create a simple Python script that saves a timestamp to a file when it starts:
# app.py
import time
from pathlib import Path
# The PVC will be mounted at /data
data_file = Path("/data/app_data.txt")
# Simulate user data — append each pod start
with data_file.open("a") as f:
f.write(f"Started at {time.time():.2f}\n")
print(f"Wrote startup timestamp to {data_file}")
print("Current file contents:")
print(data_file.read_text() if data_file.exists() else "(empty)")
Containerize it with a simple Dockerfile:
FROM python:3.11-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
2. Create the PersistentVolumeClaim
Save this as pvc.yaml:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: python-data-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
Apply it and check its status:
kubectl apply -f pvc.yaml
kubectl get pvc
You'll see the PVC in Pending state until a matching PV is provisioned. On many clusters (like minikube or kind), dynamic provisioning will automatically create a PV bound to your claim, and the status will become Bound after a few seconds.
3. Create a pod that uses the PVC
Now define a pod that mounts the PVC. Save as pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: python-data-pod
spec:
containers:
- name: python-app
image: my-python-app:latest
volumeMounts:
- name: app-storage
mountPath: /data
volumes:
- name: app-storage
persistentVolumeClaim:
claimName: python-data-claim
Pro tip: The
mountPathis where your Python code expects to find the data. Keep this path consistent across your app and the pod spec.
Start the pod and check the pod's startup output:
kubectl apply -f pod.yaml
kubectl logs python-data-pod
Expected output:
Wrote startup timestamp to /data/app_data.txt
Current file contents:
Started at 1723456789.01
4. Verify data persistence
Delete the pod, then recreate it with the same PVC:
kubectl delete pod python-data-pod
kubectl apply -f pod.yaml
kubectl logs python-data-pod
This time, the output should show two lines in the file — the old timestamp and the new one:
Current file contents:
Started at 1723456789.01
Started at 1723456792.54
The data persisted across pod restarts — the PVC worked!
Why it works: The PVC is independent of the pod. When the pod died, the PVC remained bound to the PV, which kept the underlying storage. The new pod mounted the same claim and accessed the same files.
Compare options / when to choose what
PVCs are not the only way to handle storage in Kubernetes. Let's compare common approaches:
| Approach | Use case | Persistence | Complexity | Notes |
|---|---|---|---|---|
| EmptyDir | Temporary cache for a single pod | No — cleared on pod deletion | Low | Good for scratch space, not for real data |
| HostPath | Single-node cluster (development) | Yes, tied to a node | Low | Not portable; node-specific |
| PersistentVolumeClaim | Production data across pod restarts | Yes — storage survives pod lifecycle | Medium | The standard abstraction |
| Cloud-specific (EBS, GCEPersistentDisk) | Managed cloud backend | Yes | Medium-High | Deep integration with cloud provider |
When to choose a PVC: Whenever your Python app needs durable storage that follows it across pod restarts and rescheduling. This is the default for production workloads.
When alternatives might be better: If your data is disposable (cache), use emptyDir for simplicity. If you need shared access across many pods (e.g., for collaborative editing), look into ReadWriteMany volumes or more advanced options.
Variations
- Storage Classes: Instead of specifying the exact PV, you can let a StorageClass dynamically provision storage. You can create custom classes for different performance tiers (SSD vs HDD).
- StatefulSets: For Python apps like databases or services that need stable network identity and stable storage, StatefulSets use PVCs automatically with stable names (
pvc-<pod-name>). This is ideal for leader-election patterns. - CSI drivers: The Container Storage Interface standardizes how Kubernetes interacts with storage providers. As a developer, you often only deal with PVCs; the CSI driver handles the rest behind the scenes.
Troubleshooting & edge cases
Your PVC might not bind immediately, or your pod might stay stuck in Pending. Here are common issues and fixes:
PVC stuck in Pending
Cause: No PV matches your request and dynamic provisioning is not available.
Fix:
- Check storage classes: kubectl get storageclass
- Check PVs: kubectl get pv
- Verify the storage class name matches exactly.
- Create a static PV manually if needed (for development on bare metal).
Pod stuck in ContainerCreating
kubectl describe pod python-data-pod
Look for FailedMount events. This often means:
- The PVC name is misspelled in the pod spec.
- The PVC is in a different namespace than the pod.
- The underlying storage is not reachable (e.g., NFS server down).
Access mode mismatch
If you need to scale your Python app to multiple replicas and each pod needs to read/write the same data, a ReadWriteOnce PVC won't allow that (it's only mountable on a single node). Use ReadWriteMany with a shared filesystem (NFS, GlusterFS) or use a central database instead of file-based storage.
Storage class disappeared
If your cluster admin deletes a storage class, dynamic provisioning for that class stops. Your existing PVCs still work, but new ones with that class will stay Pending.
What you learned & what's next
You've now mastered PVCs from the perspective of a Python developer. Let's recap what you can now do:
- Explain the core idea behind PVCs — that they decouple storage from pod lifecycle.
- Create a PVC in Kubernetes, mount it into a Python pod, and verify that data survives pod restarts.
- Debug common PVC binding issues.
- Choose between PVCs,
emptyDir, and other storage options based on your app's needs.
You've directly accomplished the learning objectives: you can explain the core idea behind PVCs for Python app data, and you've completed a practical exercise that demonstrates persistence across restarts.
What's next? In the next lesson, you'll learn how to actually provision the persistent volume that backs your PVC — either statically or dynamically via StorageClasses. This will give you full control over your Python app's storage in production. Let's continue!
Practice recap
In your own cluster, create a PVC and a Python pod that writes a timestamp to a file on every start. Restart the pod and verify the file grows. Then try adding a second replica and observe the access mode limitation — that will make the next lesson on PVs and StorageClasses far more intuitive.
Common mistakes
- Forgetting that a PVC is namespace-scoped: if your pod and PVC are in different namespaces, the pod won't find it and will hang in
ContainerCreating. - Using
ReadWriteOncefor a multi-replica Python service that needs shared writes; scale beyond one node and your pods won't mount the volume. - Assuming data persists if you use
emptyDir— it's cleared on pod deletion. Always choose a PVC for durable Python data. - Not checking the PVC status before referencing it in a pod; if it's
Pending, the pod will also bePending.
Variations
- Use a StorageClass with dynamic provisioning instead of manually creating PVs — just specify
storageClassNamein your PVC manifest. - If you use StatefulSets for your Python app, Kubernetes creates and binds a PVC automatically for each replica, with a stable name like
pvc-data-<pod-name>. - For cloud-agnostic portability, you can use a CSI driver (like the AWS EBS CSI driver) to handle the underlying storage while your PVC manifest stays unchanged.
Real-world use cases
- A Django app using SQLite that must preserve the database file across pod restarts.
- A data processing service that writes output files to disk for later analysis, and needs those files available after a reschedule.
- A user-upload file storage microservice that keeps uploaded files on a persistent volume shared across multiple replicas.
Key takeaways
- PVCs abstract storage, letting your Python app's data survive pod restarts and rescheduling.
- Create a PVC with a size and access mode; Kubernetes binds it to a matching PV, either statically or dynamically.
- Always reference the PVC in your pod spec using a
volumeandvolumeMountswith a consistentmountPath. - Choose
emptyDironly for disposable data; for durable data, use PVCs. - Debug PVC issues by checking
kubectl get pvc,kubectl get pv, andkubectl describe pod.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.