Skip to content

We now have a scalable, configurable application. But there’s one last piece of the puzzle we need to solve. Our Nginx Pods are stateless—they don’t need to save any data. If a Pod is deleted, we don’t lose anything important. But what about a database, a blog with user uploads, or a metrics server? The data must survive a Pod restart.

By default, the filesystem inside a container is ephemeral. When a Pod is destroyed, all the data written inside its container is lost forever. For stateful applications, this is a critical problem. We need a way to store data that persists independently of the Pod’s lifecycle.

In Kubernetes, this is achieved through an abstraction layer involving PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs). This system cleverly separates the concerns of the cluster administrator (who manages the storage infrastructure) and the developer (who consumes the storage).

  1. PersistentVolume (PV): A PersistentVolume is a piece of storage in the cluster that has been provisioned by an administrator. It is a resource in the cluster, just like a Node is a resource. A PV is a specific block of storage (like a Google Persistent Disk, an AWS Elastic Block Store volume, or an on-premise NFS share) that is made available to the cluster. The key point is that the PV has a lifecycle independent of any individual Pod.
  2. PersistentVolumeClaim (PVC): A PersistentVolumeClaim is a request for storage made by a developer. The developer doesn’t need to know the specific details of the underlying storage technology. They simply create a PVC and say, “I need 1 GiB of storage that supports being read and written by a single Pod.”
  3. The Binding Process: When a developer creates a PVC, Kubernetes looks for an available PV that meets the requirements of the claim (e.g., size, access mode). If it finds a suitable PV, it “binds” the PVC to the PV. The developer’s claim is now fulfilled, and they can use that storage in their Pods.

Analogy: Think of the administrator as a hotel manager who prepares a set of rooms (the PVs). The developer is a guest who makes a reservation (the PVC), requesting a certain type of room. The hotel system (Kubernetes) matches the reservation to an available room and gives the key to the guest.

Manually pre-provisioning PVs can be cumbersome. The modern approach is dynamic provisioning using a StorageClass.

A StorageClass provides a way for administrators to define different “classes” of storage they offer (e.g., fast-ssd, slow-hdd, backup-storage). When a developer’s PVC requests a specific StorageClass, Kubernetes automatically triggers a cloud provider or storage plugin to dynamically create a brand new PV just for that claim. Most Kubernetes environments, including Minikube, come with a default StorageClass set up, making this process seamless.

This is the relationship:

+-----------+ +-------------------------+ +-------------------+ +-------------------+
| Pod |------>| PersistentVolumeClaim |----->| PersistentVolume |----->| Actual Storage |
| | uses | (Developer's Request: | binds| (Admin's Resource)| reps.| (GCP Disk, AWS EBS|
| | | "I need 1Gi of | to | "Here is 1Gi") | | NFS, etc.) |
| | | storage") | | | | |
+-----------+ +-------------------------+ +-------------------+ +-------------------+


  1. Create a PersistentVolumeClaim (PVC) We’ll request 1 GiB of storage. Since Minikube has a default StorageClass named standard, a PV will be automatically created and bound for us.

    Create a file named pvc.yaml:

    pvc.yaml
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: my-storage-claim
    spec:
    # This requests the default storage class
    storageClassName: standard
    # Defines how the volume can be mounted. ReadWriteOnce means
    # it can be mounted as read-write by a single Node.
    accessModes:
    - ReadWriteOnce
    resources:
    requests:
    # The desired size of the storage
    storage: 1Gi

    Apply it:

    Terminal window
    kubectl apply -f pvc.yaml

    Check the status. You’ll see it’s Bound, meaning it’s ready to be used.

    Terminal window
    kubectl get pvc
    # NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
    # my-storage-claim Bound pvc-a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d 1Gi RWO standard 10s
  2. Create a Pod That Uses the PVC Now, create a Pod that mounts this claim into its filesystem.

    Create a file named pod-with-storage.yaml:

    pod-with-storage.yaml
    apiVersion: v1
    kind: Pod
    metadata:
    name: stateful-pod
    spec:
    containers:
    - name: my-app
    image: alpine # A very small linux image
    # Keep the container running with a long-running command
    command: ["/bin/sh", "-c", "sleep 3600"]
    # This is where we mount the volume into the container
    volumeMounts:
    - name: my-persistent-storage
    mountPath: /data
    # This section defines the volume source, linking it to our PVC
    volumes:
    - name: my-persistent-storage
    persistentVolumeClaim:
    claimName: my-storage-claim

    Apply it: kubectl apply -f pod-with-storage.yaml.

  3. Write Data to the Volume Let’s get a shell inside the running Pod and write a file to our mounted directory /data.

    Terminal window
    # Get a shell inside the pod
    kubectl exec -it stateful-pod -- /bin/sh
    # Inside the pod's shell, navigate to the mount point and create a file
    / # cd /data
    /data # echo "My data has persisted!" > persistent.txt
    /data # cat persistent.txt
    My data has persisted!
    /data # exit
  4. Simulate Pod Failure Now, let’s delete the Pod.

    Terminal window
    kubectl delete pod stateful-pod

    The Pod is gone. But is our data? No. The PVC and the PV it is bound to are separate objects and are still there: kubectl get pvc.

  5. Recreate the Pod and Verify the Data Let’s create a new Pod using the exact same definition. Since it refers to the same PVC, it will mount the same underlying storage.

    Terminal window
    kubectl apply -f pod-with-storage.yaml

    Now, exec into this new Pod and check for the file:

    Terminal window
    kubectl exec -it stateful-pod -- /bin/sh
    # Inside the new pod's shell
    / # cat /data/persistent.txt
    My data has persisted!

    Success! The data survived the complete destruction and recreation of the Pod.

  • For stateful applications, you need storage that exists beyond the life of a Pod.
  • You use a PersistentVolumeClaim (PVC) to request storage for your application.
  • The PVC is mounted as a volume inside your Pod’s containers.
  • In modern Kubernetes, StorageClasses handle the dynamic provisioning of PersistentVolumes (PVs) to satisfy your claims.

We have now covered all the core Kubernetes objects needed to deploy, configure, expose, and persist a complete application. The final chapter will introduce a tool that makes managing all these YAML files much, much easier.

Let me know when you’re ready for our final chapter: Chapter 6: Packaging Your Application with Helm.