Certified Kubernetes Application Developer (CKAD) #17 Volumes: emptyDir, PVC, projected, ephemeral
A container’s filesystem disappears the moment the container dies. Whether the container restarts after a crash or a rolling update brings up a new image, the files written inside don’t survive. So where do you put your data when you need to collect logs briefly, keep database files around permanently, or have two containers share the same directory? This post answers that question by working through the types of Kubernetes volumes from a hands-on perspective.
If the storage chapter of the K8s practitioner track covered the concepts of PV and PVC once, here we focus on the manifest shapes you actually write on the CKAD exam. What you need to internalize isn’t the field names — it’s the judgment of which volume to pick in which situation.
The problem volumes solve #
A Kubernetes volume is an abstraction that attaches a directory to the containers in a Pod. Depending on the kind of volume, the substance behind that directory might be the node’s temporary disk, network storage, or memory. The types that show up often on the CKAD narrow down to four: emptyDir for temporary sharing between containers, PersistentVolumeClaim for permanent data, projected for bundling several configuration sources, and generic ephemeral for defining a PVC inline inside the Pod.
Volumes are defined under spec.volumes, and each container specifies the mount path in its volumeMounts. The key point is that definition and mount are separated. A single volume can be mounted by several containers at their own paths, and this is the foundation for sharing between containers.
emptyDir: scratch space for the Pod’s lifetime #
An emptyDir is created as an empty directory when the Pod is scheduled onto a node, and it’s deleted when the Pod is removed from the node. The contents of an emptyDir survive even when a container restarts after a crash. It disappears only when the Pod itself goes away, so it outlives any individual container restart but is tied to the Pod’s lifetime.
The most common use is as a channel for two containers in the same Pod to pass files. For example, in a sidecar setup where a generator container writes a file and a server container reads it, you just mount the emptyDir on both sides.
Setting the medium: Memory option puts the directory on tmpfs (memory) rather than disk. It’s fast and leaves no trace on disk, but it consumes memory and counts toward the Pod’s memory usage. It’s used for caches or sensitive temporary files.
apiVersion: v1
kind: Pod
metadata:
name: shared-emptydir
spec:
containers:
- name: writer
image: busybox
command: ["sh", "-c", "while true; do date >> /data/out.log; sleep 5; done"]
volumeMounts:
- name: scratch
mountPath: /data
- name: reader
image: busybox
command: ["sh", "-c", "tail -f /data/out.log"]
volumeMounts:
- name: scratch
mountPath: /data
volumes:
- name: scratch
emptyDir: {}Since writer and reader both mount the same scratch volume at their own /data, the reader reads exactly the logs the writer wrote. To switch to memory backing, replace emptyDir: {} with emptyDir: { medium: Memory }.
hostPath: mounting the node disk directly (rare on the exam) #
hostPath attaches a path on the node’s filesystem directly to the Pod. It’s used for system-level work that needs access to a specific directory or device on the node, but the data only has meaning while that Pod is running on that node. If the Pod moves to a different node, the hostPath there is empty. Because of this node dependency and the associated security risk, it’s not recommended for storing app data, and you’ll rarely write it directly on the CKAD exam. Knowing the concept and the risk is enough.
PV and PVC: the standard for permanent data #
Data that must survive even after the Pod is gone, such as database files, is handled with PersistentVolume (PV) and PersistentVolumeClaim (PVC). A PV is an actual piece of storage the cluster holds, while a PVC is the user’s request: “give me this much capacity with this access mode.” The Pod doesn’t point at the PV directly; it consumes storage through the PVC. Thanks to this layer of indirection, the app manifest doesn’t need to know how the backend storage is implemented.
accessModes #
There are three access modes the PVC can request that matter.
| Mode | Abbreviation | Meaning |
|---|---|---|
| ReadWriteOnce | RWO | Read-write mount on a single node |
| ReadOnlyMany | ROX | Read-only mount on many nodes |
| ReadWriteMany | RWX | Read-write mount on many nodes |
RWO is a per-node restriction, so several Pods on the same node can mount it simultaneously. RWX requires a shared filesystem like NFS behind it, and not all storage supports it.
StorageClass and dynamic provisioning #
In the past, an administrator would create PVs ahead of time and wait for a PVC to bind to one. Now, when you specify a StorageClass, the cluster automatically creates and attaches a PV the moment you create the PVC. This is dynamic provisioning. You write the class name in the PVC’s storageClassName, and if you leave it empty, the cluster’s default StorageClass is used.
The binding between a PVC and a PV is one-to-one. Once a PV satisfying the PVC’s requested capacity, accessModes, and StorageClass conditions is selected and bound, no other PVC can use that PV. If a PVC stays in Pending, either no PV matches the conditions or dynamic provisioning isn’t working, so check the events first with kubectl describe pvc.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: db
spec:
containers:
- name: db
image: busybox
command: ["sh", "-c", "echo hello > /var/lib/data/seed; sleep 3600"]
volumeMounts:
- name: store
mountPath: /var/lib/data
volumes:
- name: store
persistentVolumeClaim:
claimName: data-pvcPointing at the PVC with persistentVolumeClaim.claimName in the Pod’s volume is all there is to the mount. The name must match the PVC name exactly; if it differs, the Pod can’t find the storage and won’t start.
projected volume: several configurations into one directory #
A projected volume combines different sources like secret, configMap, serviceAccountToken, and downwardAPI into a single directory. From the container’s point of view, the files gather under one path regardless of their origin, so configuration can be read in one place. It’s useful when you need to collect certificates, configuration values, and tokens at the same mount point.
Each source goes in as an item in the sources list, and each item specifies which key to expose under which file name.
downwardAPI: a Pod’s own information as files or env #
The downwardAPI is the channel that lets a Pod read its own metadata. A container can know which namespace it’s in, what its Pod name is, and what labels are attached to it without hardcoding any of that into the image. There are two ways to expose it: inject it as env variables, or mount it as files.
When used as env, you reference fields like metadata.name, metadata.namespace, and status.podIP with fieldRef. A container’s resource request and limit values are exposed with resourceFieldRef. When used as files, you write the field path and file path in the items of a projected or downwardAPI volume. For information with many keys, such as labels and annotations, the file approach is more natural than env.
apiVersion: v1
kind: Pod
metadata:
name: projected-demo
labels:
app: demo
tier: web
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "ls -l /etc/podinfo; cat /etc/podinfo/labels; sleep 3600"]
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
- name: bundle
mountPath: /etc/podinfo
readOnly: true
volumes:
- name: bundle
projected:
sources:
- configMap:
name: app-config
- secret:
name: app-secret
- downwardAPI:
items:
- path: labels
fieldRef:
fieldPath: metadata.labels
- path: name
fieldRef:
fieldPath: metadata.nameThis Pod sees the keys from the configMap and secret, plus its own labels and name, all as files under the single /etc/podinfo directory. At the same time it receives the namespace through the POD_NAMESPACE env. Because it shows projected and downwardAPI together in one shot, this example is a good one for internalizing the combination of the two concepts.
generic ephemeral volume: a one-line summary #
A generic ephemeral volume embeds a PVC template directly inside the Pod definition, provisioning a persistent volume on the spot that shares the Pod’s lifetime. It’s cleaned up when the Pod is deleted, so it’s used when you want StorageClass dynamic provisioning without having to create a separate PVC object in advance. Its weight on the CKAD is low, so it’s enough to know the option exists.
Exam points #
- emptyDir has the same lifetime as the Pod. It survives container restarts but disappears when the Pod is deleted. If you need sharing between containers, attach the same volume to both sides’
volumeMounts. medium: Memoryis tmpfs. Fast, but it uses memory and counts against the Pod’s memory.- PVC mounts use
persistentVolumeClaim.claimName. The name must match the PVC exactly. If the PVC isPending, start by looking at the events withdescribe. - Memorize the accessModes abbreviations. RWO is a single node, ROX is multi-node read, RWX is multi-node read-write.
- projected is a
sourceslist. It gathers configMap, secret, downwardAPI, and serviceAccountToken into one directory. - downwardAPI uses fieldRef and resourceFieldRef. Metadata is exposed with fieldRef, resource values with resourceFieldRef.
- Use dry-run to scaffold the PVC and ConfigMap skeletons, but projected and downwardAPI have no generator, so confirm the field paths with the docs or
kubectl explain pod.spec.volumes.projected.
Wrap-up #
What this post locked in:
- Starting from the premise that a container filesystem is volatile, we split the choices for where to put data by volume type.
- emptyDir. Scratch space for the Pod’s lifetime and a channel for sharing between containers. Switch to tmpfs with
medium: Memory - hostPath. Mounting the node disk directly. Unfit for app data because of node dependency and security risk
- PV/PVC. Request storage with a PVC and provision dynamically with a StorageClass. accessModes (RWO/ROX/RWX) and one-to-one binding
- projected. Combine several configuration sources into one directory
- downwardAPI. Expose a Pod’s own metadata and resources as files or env
- generic ephemeral. On-the-spot PVC provisioning tied to the Pod’s lifetime
Next: Services #
So far we’ve looked inside the Pod — how containers handle data. Now we move outside the Pod, to how traffic gets into a Pod.
#18 Services: ClusterIP, NodePort, LoadBalancer, ExternalName covers the four types of Service that create a stable access point in an environment where Pod IPs change constantly, how selectors and labels group Pods, how to quickly produce a Service with kubectl expose, and the “why isn’t this Service sending traffic to the Pod” type of question that shows up often on the exam — all built hands-on.