Volumes¶
What You'll Learn¶
- The ephemeral volume types Kubernetes attaches directly to a pod (
emptyDir,hostPath,configMap,secret,projected) and what each one is actually for - Why
hostPathis one of the most dangerous things you can put in a manifest - Why a pod's
volumes:block, on its own, never gives you durable storage — which is exactly the gap PersistentVolumes fill
Why This Matters¶
Every container filesystem is ephemeral by default: kill the container, lose everything written inside it. A volumes: block lets containers in the same pod share a filesystem, survive a container restart, or read configuration and secrets as files — but it does not, by itself, protect data from pod rescheduling. Confusing "this pod has a volume" with "this data is safe" is one of the most common storage mistakes on real clusters.
Mental Model¶
A volume is storage with a lifecycle tied to something specific — a pod, a node, or an external system. Ephemeral volumes are tied to the pod: when the pod is deleted, the volume (and usually its data) goes with it, no matter which node it lands on next.
| Volume type | Lifecycle | Backed by | Typical use |
|---|---|---|---|
emptyDir |
Pod | kubelet-managed directory on the node (or tmpfs in RAM) | Scratch space, cache, sharing files between containers in one pod |
hostPath |
Node | A path on the node's filesystem | Node-level agents, log collectors — rarely application data |
configMap |
Pod (read-only) | The ConfigMap object | Mounting config files into a container |
secret |
Pod (read-only), tmpfs-backed | The Secret object | Mounting credentials, TLS material, tokens |
projected |
Pod (read-only) | Multiple sources combined into one directory | Merging ConfigMap + Secret + downward API + service account token in one mount |
How It Works¶
emptyDir: pod-scoped scratch space¶
emptyDir is created empty when the pod is scheduled and deleted permanently when the pod is removed from the node — including when the pod is evicted or crashes past its restart budget. It's the correct choice for anything you're comfortable losing: a build cache, a temporary sort buffer, a Unix socket shared between an app container and its sidecar.
apiVersion: v1
kind: Pod
metadata:
name: cache-demo
spec:
containers:
- name: app
image: myapp:1.8.2
volumeMounts:
- name: scratch
mountPath: /tmp/cache
- name: sidecar
image: myapp-sidecar:1.8.2
volumeMounts:
- name: scratch
mountPath: /var/cache/shared
volumes:
- name: scratch
emptyDir:
sizeLimit: 1Gi
Setting sizeLimit matters: an unbounded emptyDir can fill node disk and trigger evictions for every pod on that node, not just the offender. For latency-sensitive scratch data, emptyDir.medium: Memory backs the volume with tmpfs — fast, but it counts against the pod's memory limit and disappears immediately on container restart.
hostPath: powerful and dangerous¶
hostPath mounts a path from the node's own filesystem into the pod. That means:
- Data survives pod restarts on the same node, but a reschedule to a different node sees a completely different (usually empty) directory at that path.
- Two pods on the same node using the same
hostPathshare the same underlying files — with no coordination, locking, or Kubernetes-level isolation. - A pod with write access to sensitive
hostPathlocations (/etc,/var/run/docker.sock, the kubelet's own directories) can affect or compromise the node itself. This is a standard container-escape vector and is blocked by the Restricted Pod Security Standard.
apiVersion: v1
kind: Pod
metadata:
name: node-exporter-demo
spec:
containers:
- name: node-exporter
image: prom/node-exporter:v1.8.2
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true
volumes:
- name: proc
hostPath:
path: /proc
type: Directory
Legitimate hostPath use is almost always a node-level agent (a monitoring exporter, a log shipper, a CNI or CSI driver component) reading node state read-only — not application data storage. If you find yourself reaching for hostPath to persist database files, that's the signal to move to a PersistentVolumeClaim instead.
ConfigMap, Secret, and projected volumes¶
Mounting a ConfigMap or Secret as a volume turns each key into a file in the mount directory, rather than an environment variable:
apiVersion: v1
kind: Pod
metadata:
name: configured-app
spec:
containers:
- name: app
image: myapp:1.8.2
volumeMounts:
- name: app-config
mountPath: /etc/app/config
readOnly: true
- name: tls-certs
mountPath: /etc/app/tls
readOnly: true
volumes:
- name: app-config
configMap:
name: app-config
- name: tls-certs
secret:
secretName: app-tls
secret volumes are backed by tmpfs (RAM-backed, never written to disk on the node), which is why Secrets should generally be consumed as volumes rather than environment variables when the workload can support it.
A projected volume merges several sources into a single mount point — commonly a ConfigMap, a Secret, the downward API, and a service account token together:
volumes:
- name: all-in-one
projected:
sources:
- configMap:
name: app-config
- secret:
name: app-tls
- downwardAPI:
items:
- path: "pod-labels"
fieldRef:
fieldPath: metadata.labels
- serviceAccountToken:
path: token
expirationSeconds: 3600
Why none of this survives a reschedule¶
Every volume type on this page is tied to the pod (or, for hostPath, to one specific node). If a node dies, or the scheduler evicts and reschedules the pod elsewhere, the pod comes back with a brand-new, empty ephemeral volume. There is no Kubernetes mechanism here that migrates or preserves the data — that guarantee only exists once storage is backed by a PersistentVolume, which is decoupled from any single pod's lifecycle. That's the subject of the next page.
Common Mistakes¶
- Using
hostPathfor application data (databases, uploaded files) instead of a PersistentVolumeClaim — data silently disappears on reschedule, and it's a security liability in the meantime. - Not setting
sizeLimitonemptyDir, letting one runaway pod exhaust node disk and cause cascading evictions. - Mounting a Secret as an environment variable when a volume mount was available — env vars are more likely to leak into logs, crash dumps, and
/proc/<pid>/environ. - Assuming a
configMaporsecretvolume update takes effect immediately in every consumption path — kubelet does propagate volume-mounted updates (with a short sync delay), but only for the volume mount, not for values already injected as environment variables at pod start.
Interview Questions¶
- What's the difference between
emptyDirandhostPath, and when ishostPathactually appropriate? - Why is a Secret usually mounted as a volume instead of injected as an environment variable?
- If a pod with an
emptyDirvolume is rescheduled to a different node, what happens to its data?
See Interview Prep for full answers.
Next¶
Continue to PersistentVolumes and PersistentVolumeClaims to see how Kubernetes decouples storage from any single pod's lifecycle.