Kubernetes Kubernetes

Persistent Storage: Volumes, PersistentVolumes, and Claims

Keep data alive across Pod restarts with PVCs, and understand how StorageClasses tie the pieces together.

14 min Intermediate

A container’s filesystem lasts only as long as the container. When a Pod is replaced, anything it wrote is gone. That is fine for stateless web servers and a problem for databases, uploads, and anything else that must survive. This guide shows how Kubernetes attaches durable storage to Pods.

Why container storage isn’t enough

Containers are meant to be small and disposable, so putting data inside them defeats the model. Data has to live outside the container and outside the Pod, on storage that Kubernetes attaches when a Pod starts. Kubernetes is agnostic about what that storage is: a cloud disk, a network share, or a local drive. It also doesn’t back the data up for you. That remains your job.

The storage chain

Four objects work together:

  • A StorageClass describes a kind of storage and how to create it on demand (for example “fast SSD volumes from the cloud provider”).
  • A PersistentVolume (PV) is an actual piece of storage in the cluster.
  • A PersistentVolumeClaim (PVC) is a request for storage by a user or an application: “I need 1 GiB that one node can read and write.”
  • The Pod references the claim as a volume and mounts it into a container.
Container ─ mountPath ─▶ Pod volume ─▶ PVC ─ binds to ─▶ PV ─▶ real storage
                                                          ▲
                                        StorageClass creates PVs on demand

A PVC binds to exactly one PV. The Pod never refers to the PV, only to the claim, which is what keeps applications independent of the storage details.

Request storage with a PVC

Most clusters have a default StorageClass that creates volumes on demand (called dynamic provisioning), and minikube includes one. Check yours:

kubectl get storageclass

Then ask for storage:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
kubectl apply -f pvc.yaml
kubectl get pvc,pv

The claim should become Bound, and a matching PV appears. Some storage classes wait until a Pod actually uses the claim before creating the volume, so Pending at this point isn’t necessarily an error.

Mount it in a Pod and prove persistence

apiVersion: v1
kind: Pod
metadata:
  name: storage-demo
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: data
          mountPath: /data
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: data

Write a file, destroy the Pod, recreate it, and check that the file survived:

kubectl apply -f pod.yaml
kubectl exec storage-demo -- sh -c 'echo persisted > /data/proof.txt'
kubectl delete pod storage-demo
kubectl apply -f pod.yaml
kubectl exec storage-demo -- cat /data/proof.txt

The new Pod is a completely different container, but the claim, and the data behind it, were reattached.

Access modes, reclaim policy, and static volumes

  • Access modes say how a volume can be mounted. ReadWriteOnce means read-write by a single node, so several Pods on that node can still share it. ReadOnlyMany and ReadWriteMany allow many nodes, if the storage supports it. ReadWriteOncePod restricts it to a single Pod.
  • Reclaim policy decides what happens to the volume when the claim is deleted. Dynamically provisioned volumes usually default to Delete, which removes the underlying storage and its data. Use Retain when you want to keep the data.
  • Static provisioning means an administrator creates PVs by hand and claims bind to whichever one fits. A hostPath PV, which uses a directory on a node, is a common demo, but it ties data to one machine and is only suitable for single-node development.

Databases and StatefulSets

You can mount one claim into a Deployment’s Pods, but replicas of a Deployment are interchangeable and identical, and most databases don’t work that way. A StatefulSet is built for stateful workloads. It gives each Pod a stable identity (db-0, db-1), starts and updates them in order, and can create a separate PVC for each replica:

spec:
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 5Gi

A StatefulSet handles identity and storage, not data replication. Making replicas consistent with each other is the database’s job, which is why running databases on Kubernetes is harder than running stateless services. Many teams use a managed database or a database operator, and keep the stateless applications in the cluster.

Popular Guides