Understand PVCs for Python Data

Understand PVCs for Python app data — Kubernetes for Python Developers tutorial.

Focus: understand pvcs for python app data

Sponsored

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 ./uploads disappear 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:

  1. PVC creation — You submit a manifest that describes your storage request: 1 GiB, ReadWriteOnce, storage class standard.
  2. 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.
  3. 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.
  4. Pod usage — You reference the PVC in a Pod spec via a volume and mount it at a path in the container (e.g., /app/data).
  5. 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 mountPath is 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 ReadWriteOnce for 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 be Pending.

Variations

  1. Use a StorageClass with dynamic provisioning instead of manually creating PVs — just specify storageClassName in your PVC manifest.
  2. 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>.
  3. 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 volume and volumeMounts with a consistent mountPath.
  • Choose emptyDir only for disposable data; for durable data, use PVCs.
  • Debug PVC issues by checking kubectl get pvc, kubectl get pv, and kubectl describe pod.

Sponsored

Sponsored

Discussion

Questions, corrections, and tips help everyone reading this page.

0 comments

Add a comment

Shown publicly with your comment.

Be constructive · max 4,000 characters

No comments yet — start the thread.

Related tutorials, quizzes, and articles for this topic.