Containerised applications have become a dominant pattern in modern IT infrastructure, and Kubernetes has emerged as the de facto standard for orchestrating them at scale. One area that often trips up newcomers is storage: containers are designed to be ephemeral, yet many real-world applications need durable, persistent data. This article walks through how Kubernetes approaches storage, from temporary scratch space to fully persistent volumes.
What Makes Container Storage Different?
Containers package everything an application needs to run — code, runtime, libraries, and configuration — into a portable, isolated unit. Unlike traditional virtual machines, containers share the host operating system kernel, which makes them lightweight and fast to start, clone, and terminate.
This design makes containers excellent for workloads with unpredictable or spiky demand, particularly web-facing services where Kubernetes can automatically scale pods up and down. However, it also means containers are stateless by default: when a container or pod is destroyed, any data written inside it is gone.
In Kubernetes, containers are grouped into pods — the smallest deployable unit. Kubernetes manages the full lifecycle of pods, including scheduling, load balancing, health checks, and their relationship to underlying hardware, including storage.
Ephemeral Storage: emptyDir
The simplest form of storage in Kubernetes is ephemeral storage, created using the emptyDir volume type. When a pod is assigned to a node, Kubernetes creates an empty directory on that node’s filesystem. All containers in the pod can read from and write to it.
The key characteristics of emptyDir:
- Created fresh when a pod starts on a node.
- Deleted permanently when the pod is removed from that node.
- Shared between containers within the same pod.
- Portable in the sense that no external configuration is required, but not persistent — data does not survive pod termination.
emptyDir is useful for scratch space, caching, or sharing temporary files between containers in a pod, but it is not suitable for any data that needs to outlive the pod.
volumes:
- name: scratch-space
emptyDir: {}Code language: CSS (css)
Persistent Storage: Persistent Volumes and Persistent Volume Claims
For data that must survive beyond the life of a pod, Kubernetes provides Persistent Volumes (PVs) and Persistent Volume Claims (PVCs). These two objects work together to decouple storage provisioning from storage consumption.
Persistent Volumes (PVs)
A Persistent Volume is a cluster-level resource created and managed by a storage administrator. It represents a piece of storage in the cluster — this could be an NFS share, a cloud block storage device, an iSCSI target, or any other supported backend. A PV defines:
- Capacity — how much storage is available.
- Access modes — for example, ReadWriteOnce (one node reads/writes), ReadOnlyMany (many nodes read), or ReadWriteMany (many nodes read/write).
- Volume plugin or CSI driver — how Kubernetes communicates with the underlying storage system.
- Reclaim policy — what happens to the volume after the claim is released (Retain, Recycle, or Delete).
- Connection details — paths, IP addresses, credentials, and so on.
PVs are not namespaced and are not portable across Kubernetes clusters. They represent real infrastructure.
Persistent Volume Claims (PVCs)
A Persistent Volume Claim is a request for storage made by an application or developer. Rather than specifying low-level storage details, a PVC describes what the application needs: how much capacity, what access mode, and optionally which storage class to use.
Kubernetes matches the PVC to an available PV that satisfies the requirements and binds them together. Once bound, the pod can use the storage through the PVC without needing to know anything about the underlying infrastructure.
PVCs are defined in the pod’s YAML manifest, which means the claim travels with the application and maintains the portability principle. A minimal PVC definition might look like this:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-app-storage
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
Why Decouple PVs and PVCs?
The separation serves two important purposes:
- Portability — applications express what they need without being tightly coupled to specific storage infrastructure. The same PVC definition can work on a local development cluster and in a cloud environment, as long as compatible storage is available.
- Role separation — storage administrators manage PVs; developers manage PVCs. Neither group needs to understand the other’s domain in detail.
Deployments vs. StatefulSets: Shared vs. Dedicated Storage
When you scale an application using a Kubernetes Deployment, multiple replica pods share a single PVC. This works well for stateless applications or read-only workloads, but it can cause problems — including data corruption or crashes — when multiple pods write to the same volume simultaneously, depending on the access mode and storage backend.
For stateful applications such as databases, Kubernetes provides StatefulSets. StatefulSets give each pod its own dedicated PVC using a volumeClaimTemplates definition. When the StatefulSet scales, each new pod gets its own unique persistent volume, preventing write conflicts and preserving data identity across restarts.
Storage Classes and Dynamic Provisioning
Manually pre-provisioning PVs for every application is operationally expensive. Storage Classes solve this through dynamic provisioning: when a PVC is created and references a Storage Class, Kubernetes automatically provisions a new PV to satisfy the claim.
A Storage Class definition specifies:
- Provisioner — the volume plugin or CSI driver responsible for creating volumes (for example,
kubernetes.io/aws-ebsor a third-party CSI driver). - Parameters — provider-specific settings such as disk type, replication factor, or encryption.
- Reclaim policy — what to do with dynamically provisioned volumes when the PVC is deleted.
- Volume binding mode — whether to provision storage immediately or wait until a pod is scheduled.
It is good practice to designate one Storage Class as the default for the cluster. Any PVC that does not specify a storage class will be bound against the default, simplifying application manifests for developers who do not need to specify storage details explicitly.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
Container Storage Interface (CSI)
The Container Storage Interface (CSI) is a standardised API that allows storage vendors to write drivers that work with any container orchestration system, including Kubernetes, without modifying Kubernetes core code. Before CSI, storage plugins had to be compiled directly into Kubernetes, which made updates slow and risky.
With CSI, storage vendors — cloud providers, SAN/NAS vendors, and software-defined storage projects alike — can ship and update their drivers independently. A Storage Class references a CSI driver by name, and Kubernetes delegates volume lifecycle operations (create, attach, mount, snapshot, and so on) to that driver.
Other Storage Options: Host Path and Local Persistent Volumes
Kubernetes supports additional storage types, though they carry significant trade-offs:
hostPath
A hostPath volume mounts a file or directory from the host node’s filesystem directly into a pod. This is simple but breaks portability: if the pod is rescheduled to a different node, the data on the original node is no longer accessible. hostPath is generally limited to single-node development environments or specific system-level use cases, and should be avoided in production deployments.
Local Persistent Volumes
Local persistent volumes allow you to use locally attached block, file, or object storage on a node as a Kubernetes PV, but with proper volume binding that ensures pods are always scheduled to the node where the data lives. This can be used to build distributed storage systems on top of Kubernetes. Projects such as Rook take this approach to create containerised, cloud-native storage pools (for example, Ceph running inside Kubernetes). The trade-off is that node failure can make data unavailable, so replication at the storage layer becomes essential.
Choosing the Right Storage Approach
| Scenario | Recommended Approach |
|---|---|
| Temporary scratch space or inter-container sharing | emptyDir |
| Stateless application needing persistent data | PVC + PV (or dynamic StorageClass) |
| Stateful application (database, message queue) | StatefulSet with volumeClaimTemplates |
| Cloud-managed storage with auto-provisioning | StorageClass with cloud CSI driver |
| Single-node development or debugging | hostPath |
| Distributed storage built on Kubernetes nodes | Local Persistent Volumes + project like Rook |
Key Takeaways
- Containers are ephemeral by design; use
emptyDironly for temporary data. - Persistent Volumes (PVs) define available storage; Persistent Volume Claims (PVCs) request storage for applications.
- Decoupling PVs and PVCs preserves portability and enforces role separation between operators and developers.
- Storage Classes enable dynamic provisioning, eliminating the need to pre-create PVs manually.
- CSI drivers standardise how Kubernetes communicates with diverse storage backends.
- Use StatefulSets instead of Deployments when each pod needs its own dedicated persistent storage.
- Avoid
hostPathin production; it undermines pod portability and is a security risk.
This article is based on foundational Kubernetes storage concepts as documented in the Kubernetes project. For the latest API specifications and supported storage backends, refer to the official Kubernetes storage documentation.