# emptyDir Volume - Temporary storage# Data is deleted when Pod is terminatedapiVersion:v1kind:Podmetadata:name:emptydir-demolabels:app:storage-demospec:containers:-name:writerimage:busybox:1.36command:["/bin/sh","-c"]args:-|while true; doecho "Writer: $(date)" >> /cache/data.txtecho "Written: $(tail -1 /cache/data.txt)"sleep 5donevolumeMounts:-name:cachemountPath:/cache-name:readerimage:busybox:1.36command:["/bin/sh","-c"]args:-|sleep 3while true; doecho "Reader: Last line from cache:"tail -1 /cache/data.txtsleep 5donevolumeMounts:-name:cachemountPath:/cachevolumes:-name:cacheemptyDir:{}# Empty directory on the node# Can also use:# emptyDir:# medium: Memory # Use tmpfs (RAM-backed)# sizeLimit: 100Mi
persistent-volume.yaml — PersistentVolume and PVC¶
# PersistentVolume - Cluster-wide storage resource# Note: In production, PVs are usually provisioned automatically by StorageClass# This is a manual (static) provisioning exampleapiVersion:v1kind:PersistentVolumemetadata:name:pv-hostpathlabels:type:localspec:capacity:storage:1GiaccessModes:-ReadWriteOnce# Can be mounted by one node at a timepersistentVolumeReclaimPolicy:Retain# Keep data when PVC is deletedhostPath:path:/mnt/data# Path on the node (for testing only)type:DirectoryOrCreate---# PersistentVolumeClaim - Request for storageapiVersion:v1kind:PersistentVolumeClaimmetadata:name:pvc-hostpathspec:accessModes:-ReadWriteOnceresources:requests:storage:500Mi# Request 500Mi, will bind to 1Gi PV# selector: # Optional: select specific PV by labels# matchLabels:# type: local---# Pod using the PVCapiVersion:v1kind:Podmetadata:name:pod-with-pvclabels:app:storage-demospec:containers:-name:appimage:busybox:1.36command:["/bin/sh","-c"]args:-|echo "Pod started at: $(date)" >> /data/log.txtecho "=== Current log file ==="cat /data/log.txtecho "========================"echo "Data persists across Pod restarts!"sleep 3600volumeMounts:-name:persistent-storagemountPath:/datavolumes:-name:persistent-storagepersistentVolumeClaim:claimName:pvc-hostpath
# Dynamic provisioning using StorageClass# PersistentVolume is created automatically# Note: Requires a StorageClass to be available (like local-path in kind)apiVersion:v1kind:PersistentVolumeClaimmetadata:name:pvc-dynamicspec:accessModes:-ReadWriteOncestorageClassName:standard# Use "local-path" in kind with local-path-provisionerresources:requests:storage:2Gi---# Deployment using dynamic PVCapiVersion:apps/v1kind:Deploymentmetadata:name:app-with-storagelabels:app:storage-demospec:replicas:1# Only 1 replica because of ReadWriteOnceselector:matchLabels:app:storage-demotemplate:metadata:labels:app:storage-demospec:containers:-name:appimage:nginx:1.25-alpineports:-containerPort:80volumeMounts:-name:datamountPath:/usr/share/nginx/htmllifecycle:postStart:exec:command:-/bin/sh--c-|if [ ! -f /usr/share/nginx/html/index.html ]; thenecho "<h1>Hello from persistent storage!</h1>" > /usr/share/nginx/html/index.htmlecho "<p>Pod: $HOSTNAME</p>" >> /usr/share/nginx/html/index.htmlecho "<p>Started: $(date)</p>" >> /usr/share/nginx/html/index.htmlfivolumes:-name:datapersistentVolumeClaim:claimName:pvc-dynamic---# Service to access the appapiVersion:v1kind:Servicemetadata:name:storage-demo-svcspec:selector:app:storage-demoports:-port:80targetPort:80type:ClusterIP
statefulset-storage.yaml — StatefulSet with storage¶
# StatefulSet with volumeClaimTemplates# Each Pod gets its own PersistentVolumeClaim# PVCs are named: <volumeClaimTemplate name>-<statefulset name>-<ordinal>apiVersion:v1kind:Servicemetadata:name:postgres-headlesslabels:app:postgresspec:clusterIP:None# Headless service for StatefulSetselector:app:postgresports:-port:5432name:postgres---apiVersion:apps/v1kind:StatefulSetmetadata:name:postgresspec:serviceName:postgres-headlessreplicas:3selector:matchLabels:app:postgrestemplate:metadata:labels:app:postgresspec:containers:-name:postgresimage:postgres:16-alpineports:-containerPort:5432name:postgresenv:-name:POSTGRES_PASSWORDvalue:mysecretpassword-name:PGDATAvalue:/var/lib/postgresql/data/pgdatavolumeMounts:-name:datamountPath:/var/lib/postgresql/dataresources:requests:memory:"256Mi"cpu:"250m"limits:memory:"512Mi"cpu:"500m"volumeClaimTemplates:# Creates PVC for each Pod-metadata:name:dataspec:accessModes:["ReadWriteOnce"]storageClassName:standard# Use "local-path" in kindresources:requests:storage:5Gi# This creates:# Pod: postgres-0 with PVC: data-postgres-0# Pod: postgres-1 with PVC: data-postgres-1# Pod: postgres-2 with PVC: data-postgres-2## Each Pod gets its own persistent storage that survives restarts# PVCs are NOT deleted when StatefulSet is deleted (manual cleanup needed)
# Docker Compose with volumesservices:# Application with persistent dataapp:image:nginx:1.25-alpinevolumes:-app-data:/usr/share/nginx/html# Named volume (persistent)-./config:/etc/nginx/conf.d:ro# Bind mount (local file)ports:-"8080:80"# Database with persistent storagedatabase:image:postgres:16-alpineenvironment:POSTGRES_PASSWORD:secretpasswordPGDATA:/var/lib/postgresql/data/pgdatavolumes:-db-data:/var/lib/postgresql/data# Named volumeports:-"5432:5432"# Temporary cache (no persistent storage needed)cache:image:redis:7.2-alpine# No volumes - data is ephemeral# Similar to emptyDir in Kubernetesvolumes:app-data:# Docker manages these volumesdb-data:# Key Points:# 1. Named volumes (app-data, db-data) = PersistentVolumeClaims in K8s# 2. Volumes are automatically created and managed by Docker# 3. Data persists across container restarts# 4. Bind mounts (./config) = ConfigMaps or hostPath in K8s# 5. No volume = ephemeral storage (like emptyDir)# Kubernetes Equivalent:# - app-data volume → PVC with 1Gi storage# - db-data volume → PVC with 5Gi storage (or StatefulSet)# - ./config bind mount → ConfigMap mounted as volume# - cache (no volume) → emptyDir or no volume (ephemeral)# Storage Management:# Compose: docker volume ls, docker volume rm# K8s: kubectl get pvc, kubectl delete pvc# Backup/Restore:# Compose: docker run --volumes-from <container> to backup# K8s: VolumeSnapshots, backup tools, or manual copy