Skip to main content
RunBook Academy

KubernetesVIII · PodsPods

Volumes in Pods — emptyDir, hostPath, projected, and CSI

Intermediate⏱ ~18 minkubectl

What you'll learn

  • Configure volumes in spec.volumes and mount them in containers
  • Distinguish emptyDir, hostPath, projected, and CSI volumes
  • Reason about volume lifecycle relative to Pod lifecycle
  • Avoid the common misconfigurations (hostPath, subPath)

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

Volumes in a Pod spec tie filesystem state to a Pod’s lifecycle. Some volume types are ephemeral (emptyDir); some are durable (CSI/PVC); some are projections of cluster objects (ConfigMap, Secret). This lesson covers the volume types that matter in production, the lifecycle semantics of each, and the discipline around hostPath and subPath.

How volumes work

A volume in a Pod has three parts:

  1. Specspec.volumes declares the volume’s name and type. The type determines where the data lives (empty directory on the node, a directory on the host, a CSI- provisioned network filesystem, etc.).
  2. Mountspec.containers[*].volumeMounts (or initContainers[*].volumeMounts) declares which containers mount the volume and at what path.
  3. Lifecycle — the kubelet creates the volume when the Pod starts and removes it when the Pod terminates (for ephemeral types) or releases it back to the storage system (for durable types).
flowchart LR
    Pod --> Vol["spec.volumes: data (emptyDir)"]
    Vol --> MountA["Container A: mount /var/data"]
    Vol --> MountB["Container B: mount /var/cache"]
    Pod --> Kubelet["kubelet creates volume on node"]
    Kubelet -->|"Pod terminates"| Cleanup["Volume deleted (emptyDir)"]

emptyDir — ephemeral scratch

volumes:
- name: scratch
  emptyDir: {}

An emptyDir is a directory on the node’s local filesystem that exists for the lifetime of the Pod. When the Pod is created, the directory is empty. When the Pod terminates, the directory is deleted.

Three sub-options:

  • sizeLimit: a maximum size; if exceeded, the Pod is evicted. Useful to prevent runaway log fills.
  • medium: Memory: a tmpfs (RAM-backed) directory. Fast; lost on node reboot.
  • medium: "" (default): node-local disk. Backed by the node’s filesystem; lost on node reboot.

Use cases:

  • Scratch space for a workload that needs writable disk (a cache, a build directory).
  • Sidecar communication: one container writes a file, another reads it (e.g., a log shipper reading a log file produced by the main container).
  • Init container output: an init container computes a value, writes it to a shared emptyDir, the main container reads it.
flowchart LR
    Init[Init container] -->|"writes /tmp/result"| Empty["emptyDir /shared"]
    Empty --> Main[Main container]
    Empty --> Side[Sidecar]

hostPath — escape to the node

volumes:
- name: host-etc
  hostPath:
    path: /etc/something
    type: Directory

hostPath mounts a directory or file from the node’s filesystem into the Pod. The Pod sees the same data as the host processes.

Use cases (legitimate):

  • Node agents: log shippers reading /var/log, node exporters reading /proc, CNI plugins reading /etc/cni/net.d.
  • DaemonSets that need host access: monitoring agents, storage agents.

Use cases (illegitimate):

  • Application data persistence: use a PVC instead.
  • Sharing configuration across Pods: use a ConfigMap.
  • Anything that breaks Pod portability: a Pod with a hostPath only works on nodes with the expected path.
volumes:
- name: data
  hostPath:
    path: /mnt/data/web
    type: DirectoryOrCreate

The above is a common anti-pattern. The Pod will only work on nodes that have /mnt/data/web; on other nodes, the kubelet creates it (with DirectoryOrCreate) and the data is local to the node. If the Pod is rescheduled, it loses its data.

projected — composite volumes

volumes:
- name: config
  projected:
    sources:
    - configMap:
        name: app-config
    - secret:
        name: app-secret
    - downwardAPI:
        items:
        - path: pod-name
          fieldRef:
            fieldPath: metadata.name
        - path: pod-ip
          fieldRef:
            fieldPath: status.podIP
    - serviceAccountToken:
        path: token
        audience: api
        expirationSeconds: 3600

A projected volume is a composite of multiple sources, mounted as a single directory. Each source contributes one or more files. Common sources:

  • configMap: each key becomes a file named <key> in the directory.
  • secret: each key becomes a file (with the value as the contents).
  • downwardAPI: expose Pod metadata as files (Pod name, namespace, IP, labels, annotations).
  • serviceAccountToken: project a token bound to the Pod’s ServiceAccount, with a configurable audience and lifetime.

Use cases:

  • Configuration bundles: combine ConfigMap + Secret + DownwardAPI into a single volume the application reads.
  • Service-account tokens: project short-lived tokens for the application to authenticate to the API server (e.g., a sidecar that calls the Kubernetes API).
  • Hot-reload configuration: mount ConfigMap as a projected volume; the kubelet updates the projected files when the ConfigMap changes (within the configMapAndSecretReloadDetectionStrategy window).

CSI / PVC — durable storage

volumes:
- name: data
  persistentVolumeClaim:
    claimName: web-data

A persistentVolumeClaim references a PersistentVolumeClaim in the same namespace. The PVC was bound to a PersistentVolume by the storage controller (or a dynamic provisioner via the StorageClass). The PV was provisioned by a CSI driver.

Production discipline: CSI / PVC is the right tool for any data that must survive a Pod reschedule.

flowchart LR
    Pod -->|claimName| PVC["PersistentVolumeClaim"]
    PVC -->|bound to| PV["PersistentVolume"]
    PV -->|provisioned by| SC["StorageClass"]
    SC -->|driver| CSI[CSI plugin]
    CSI -->|provisioning| Backend["Network storage<br/>(EBS, Ceph, NFS, ...)"]

The full flow (Part XLIX-Kubernetes-Volumes covers in depth):

  1. Pod spec references a PVC.
  2. PVC references a StorageClass and asks for a size.
  3. The storage provisioner creates a PV backed by the CSI driver’s storage.
  4. The PV is bound to the PVC.
  5. The kubelet on the chosen node mounts the PV into the Pod.
  6. The Pod reads/writes to the mount path; data persists in the backend storage.

subPath — escape a single file

volumes:
- name: config
  configMap:
    name: app-config
containers:
- name: app
  volumeMounts:
- name: config
  mountPath: /etc/app/app.conf
  subPath: app.conf

subPath mounts a single file or subdirectory from a volume into a container, rather than the entire volume. Useful when:

  • The volume is a ConfigMap with multiple keys and you want only one.
  • The volume is a PVC and you want a subdirectory.
volumes:
- name: data
  emptyDir: {}
containers:
- name: app
  volumeMounts:
  - name: data
    mountPath: /cache
    subPath: cache-v1

This creates /cache as a subdirectory of the emptyDir without exposing the emptyDir’s other contents.

The volume lifecycle

TypeLifecycleWhen data is deleted
emptyDirPodWhen Pod terminates
hostPathNodeWhen manually deleted (Pod termination does not delete)
projectedPod (ConfigMap/Secret sources are cluster-wide)When Pod terminates (the projection is recreated each Pod start)
PVC/CSIIndependent of PodWhen PVC is deleted and reclaimPolicy applies

The key distinction:

  • Pod-scoped (emptyDir, projected): data dies with the Pod. Good for scratch, sidecar communication, ephemeral config.
  • Node-scoped (hostPath): data persists on the node. Used for node agents; broken for application portability.
  • Cluster-scoped (PVC/CSI): data is independent of Pod and node. The right tool for durable storage.

Cross-course references

  • The Linux course part XVII-Linux-RAID and XVIII-Linux-Storage cover block storage; CSI volumes are the cluster-level equivalent.
  • The Docker course part XXXII-Docker-Storage covers Docker volumes; Kubernetes PVCs are the cluster-level equivalent.
  • The Proxmox course part XXXV-Linux-Storage covers shared storage; CSI plugins like Ceph RBD and NFS are the cluster-level equivalent.

Quiz

Knowledge check · 4 questions

  1. Q1. Which volume type is the worst choice for application Pods that need persistent storage across reschedules?

  2. Q2. A ConfigMap mounted with `subPath: app.conf` will pick up ConfigMap updates without a Pod restart.

  3. Q3. A workload writes to an `emptyDir` for a local cache. The cache grows unbounded over time and fills the node's root filesystem. Diagnose what happens and how to prevent it.

    Application Pod `web-7c8` mounts `emptyDir: {}` at `/cache`. The application writes to `/cache/index` as a local cache. The cache grows over time. After a week, the Pod's emptyDir has consumed 50 GB on the node's root partition.

  4. Q4. When is `projected` volume the right choice instead of mounting a ConfigMap directly?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Set sizeLimit on every emptyDir in production. Unbounded emptyDirs are a node-stability risk.
  • Avoid hostPath for application Pods. Reserve it for node agents. PSS restricted rejects most hostPath types.
  • Use PVC for data that must survive reschedules. CSI- provisioned storage is the standard.
  • Mount ConfigMaps as volumes (not subPath). subPath breaks kubelet’s refresh; the container sees stale data until restart.
  • Use projected volumes for config bundles. One mount point with multiple sources; the application reads from a single directory.