Kubernetes volumes cheatsheet.

Volume types

  • emptyDir: ephemeral, deleted with pod.
  • hostPath: bind mount from node (avoid in multi-node).
  • configMap / secret: inject configs.
  • persistentVolumeClaim (PVC): durable storage.
  • csi: CSI driver-backed (cloud disks, NFS, etc).

emptyDir

spec:
  containers:
    - name: app
      volumeMounts:
        - { name: cache, mountPath: /cache }
  volumes:
    - name: cache
      emptyDir:
        sizeLimit: 1Gi
        medium: Memory     # tmpfs

Per-pod scratch. Shared between containers in same pod.

PVC + PV

apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: data }
spec:
  accessModes: [ReadWriteOnce]
  resources:
    requests: { storage: 10Gi }
  storageClassName: gp3

Use in pod:

spec:
  containers:
    - name: db
      volumeMounts: [{ name: data, mountPath: /var/lib/data }]
  volumes:
    - name: data
      persistentVolumeClaim: { claimName: data }

Access modes

  • ReadWriteOnce (RWO): one node.
  • ReadOnlyMany (ROX): many nodes read-only.
  • ReadWriteMany (RWX): many nodes read-write (needs NFS / EFS / Filestore).
  • ReadWriteOncePod (RWOP): single pod.

Most cloud disks are RWO. For sharing across pods, use object storage or NFS.

StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: gp3 }
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  throughput: "125"
  iops: "3000"
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true

WaitForFirstConsumer delays binding until pod is scheduled (zone-aware).

Default StorageClass

kubectl patch storageclass gp3 \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

PVCs without storageClassName use the default.

Static PV (pre-provisioned)

apiVersion: v1
kind: PersistentVolume
metadata: { name: nfs-pv }
spec:
  capacity: { storage: 50Gi }
  accessModes: [ReadWriteMany]
  nfs:
    server: nfs.example.com
    path: /exports/data
  persistentVolumeReclaimPolicy: Retain

Then bind a PVC. Usually you don’t need this — dynamic provisioning is preferred.

Resize PVC

spec:
  resources:
    requests: { storage: 20Gi }    # increase from 10Gi

Requires allowVolumeExpansion: true in StorageClass.

kubectl edit pvc data
# Save, then pod may need restart to see new size

Snapshot + restore

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata: { name: snap-1 }
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data

Restore:

apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: restored }
spec:
  storageClassName: gp3
  dataSource:
    name: snap-1
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  resources:
    requests: { storage: 10Gi }
  accessModes: [ReadWriteOnce]

StatefulSet volume

spec:
  volumeClaimTemplates:
    - metadata: { name: data }
      spec:
        accessModes: [ReadWriteOnce]
        resources: { requests: { storage: 10Gi } }

Creates one PVC per replica (web-0, web-1…).

Volumes with CSI

Most modern storage uses CSI. Examples:

  • AWS EBS: ebs.csi.aws.com
  • GCP PD: pd.csi.storage.gke.io
  • Azure Disk: disk.csi.azure.com
  • Longhorn (self-hosted): driver.longhorn.io
  • Rook/Ceph: rook-ceph.rbd.csi.ceph.com

Local PV

apiVersion: v1
kind: PersistentVolume
metadata: { name: local-pv }
spec:
  capacity: { storage: 100Gi }
  accessModes: [ReadWriteOnce]
  storageClassName: local-storage
  local: { path: /mnt/disks/ssd1 }
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - { key: kubernetes.io/hostname, operator: In, values: [node-1] }

Pod stuck on specific node. High-perf use case.

hostPath (avoid)

volumes:
  - name: data
    hostPath:
      path: /var/data
      type: Directory

Pod stuck on the node; data lost if node lost. Acceptable for cluster-level tools (logs, metrics agents).

projected volumes

volumes:
  - name: combined
    projected:
      sources:
        - configMap: { name: app-config }
        - secret: { name: app-secrets }
        - downwardAPI:
            items:
              - { path: "namespace", fieldRef: { fieldPath: metadata.namespace } }

Single mount with multiple sources.

Common mistakes

  • RWO PVC + multiple replicas — only one pod schedules.
  • Default StorageClass deletes volume on PVC delete (reclaimPolicy: Delete).
  • Cross-AZ PVC + pod scheduled to wrong zone — use WaitForFirstConsumer.
  • Resizing PVC < current size — invalid.
  • StatefulSet PVCs leak after delete — manual cleanup needed.

Read this next

If you want my storage templates (EBS, NFS, Longhorn), they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .