By default, data inside a container disappears along with the container. Kubernetes provides several levels of storage abstraction — from ephemeral to fully persistent.
The Problem: Pod Dies, Data Is Gone
A container’s filesystem is ephemeral. If a Pod restarts — the database is empty, uploaded files are gone, logs have vanished. For stateful applications this is unacceptable.
The solution is Volumes: external storage that outlives a pod.
emptyDir: Temporary Storage
Created with the Pod, destroyed with the Pod. Useful for temporary files and data sharing between containers in the same Pod.
apiVersion: v1
kind: Pod
metadata:
name: app-with-temp-storage
spec:
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: temp-data
mountPath: /tmp/cache
- name: sidecar
image: busybox
volumeMounts:
- name: temp-data
mountPath: /shared
volumes:
- name: temp-data
emptyDir: {}
Both containers see the same files. Data lives as long as the Pod lives.
hostPath: Mounting a Node Directory
Mounts a directory from the host machine (Worker Node) into the Pod. Data survives pod restarts, but is tied to a specific node.
volumes:
- name: host-logs
hostPath:
path: /var/log/myapp
type: DirectoryOrCreate
Use cases: accessing host devices, specific dev scenarios. Not recommended in production — the Pod becomes node-dependent.
PersistentVolume and PersistentVolumeClaim
Full-featured persistent storage. Consists of two objects:
- PersistentVolume (PV) — the actual storage resource. Created by an administrator or automatically (StorageClass). Describes capacity, type, and access parameters.
- PersistentVolumeClaim (PVC) — a storage request from an application. The developer describes how much space and what type is needed. K8s finds a matching PV.
# persistent-volume.yaml (usually created by an administrator)
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgres-pv
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce # single Pod with read/write
persistentVolumeReclaimPolicy: Retain
storageClassName: standard
hostPath:
path: /data/postgres # for minikube/dev; in prod use NFS, cloud disk, etc.
# persistent-volume-claim.yaml (created by the developer)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 5Gi
# Using the PVC in a Pod/Deployment
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
kubectl apply -f persistent-volume.yaml
kubectl apply -f persistent-volume-claim.yaml
kubectl get pv
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS STORAGECLASS
# postgres-pv 10Gi RWO Retain Bound standard
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS
# postgres-pvc Bound postgres-pv 10Gi RWO standard
Access Modes
| Mode | Description |
|---|---|
| ReadWriteOnce (RWO) | Single node, read+write |
| ReadOnlyMany (ROX) | Many nodes, read-only |
| ReadWriteMany (RWX) | Many nodes, read+write (NFS, CephFS) |
StorageClass: Dynamic Provisioning
Manually creating a PV for every PVC is impractical. StorageClass automates storage creation.
# storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iopsPerGB: "10"
encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
Now a PVC with storageClassName: fast-ssd automatically creates an EBS disk in AWS:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
# Available StorageClasses in the cluster
kubectl get storageclass
# NAME PROVISIONER AGE
# standard (default) docker.io/hostpath 10d
# fast-ssd kubernetes.io/aws-ebs 5d
StatefulSet for Stateful Applications
A regular Deployment is not suitable for databases: pods are interchangeable, have no stable names, and share PVCs.
StatefulSet solves this:
- Stable pod names:
postgres-0,postgres-1,postgres-2 - Guaranteed startup and shutdown order
- Each Pod gets its own PVC (VolumeClaimTemplate)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-headless
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-ssd
resources:
requests:
storage: 10Gi
Each of the three pods gets its own PVC: data-postgres-0, data-postgres-1, data-postgres-2. When a pod is deleted, the PVC is preserved — data is not lost.
kubectl get statefulset
# NAME READY AGE
# postgres 3/3 5m
kubectl get pvc
# NAME STATUS VOLUME CAPACITY
# data-postgres-0 Bound pvc-abc... 10Gi
# data-postgres-1 Bound pvc-def... 10Gi
# data-postgres-2 Bound pvc-ghi... 10Gi
💬 Comments (0)
No comments yet
Be the first to share your opinion about this article!