Storage is where a lot of CKAD candidates lose easy points. The “Application Design and Build” domain — about 20% of the Certified Kubernetes Application Developer (CKAD) exam — explicitly calls out “utilize persistent and ephemeral volumes,” and the tasks that test it are pure YAML plumbing: attach an emptyDir so two containers can share files, request durable storage with a PersistentVolumeClaim, mount a ConfigMap at a specific path. None of it is conceptually hard, but under a two-hour clock the difference between a pass and a fail is whether you can write the volume-and-mount pairing from memory without reaching for the docs.
This guide makes volumes mechanical. By the end you’ll be able to look at a scenario and decide instantly whether it needs an ephemeral volume, a persistent one, or a mounted config source — and produce the manifest cleanly the first time. It focuses on the developer’s view of storage; for the cluster-admin side of provisioning (reclaim policies, dynamic provisioning internals) see the companion CKA storage guide.
The Two Halves of Every Volume
Every volume in Kubernetes is wired up in two places, and forgetting the second one is the most common exam mistake:
spec.volumes— declares the volume and where its data comes from (anemptyDir, apersistentVolumeClaim, aconfigMap, etc.). This lives at the Pod level.spec.containers[].volumeMounts— mounts that named volume into a container at a path.
The two are joined by a shared name. A volume with no mount does nothing; a mount referencing a volume name that doesn’t exist fails scheduling.
apiVersion: v1
kind: Pod
metadata:
name: shared-scratch
spec:
containers:
- name: writer
image: busybox
command: ["sh", "-c", "echo hello > /data/msg; sleep 3600"]
volumeMounts:
- name: scratch # must match the volume name below
mountPath: /data
volumes:
- name: scratch # the declaration
emptyDir: {}
Read that pattern until it’s reflex: declare in volumes, consume in volumeMounts, joined by name. Almost every storage task on the exam is a variation of it.
Ephemeral Volumes: emptyDir
An emptyDir is created empty when a Pod is assigned to a node and deleted permanently when the Pod is removed from that node. It survives container restarts within the Pod but not Pod rescheduling. That lifecycle makes it the go-to for:
- Scratch space — temporary files, caches, sort buffers.
- Sharing files between containers in the same Pod — the classic sidecar pattern, where one container writes and another reads.
volumes:
- name: cache
emptyDir:
sizeLimit: 500Mi # optional cap
An exam-worthy twist: set emptyDir.medium: Memory to back the volume with tmpfs (RAM) instead of disk — faster, but it counts against the container’s memory limit and is wiped on reboot. If a task says “use an in-memory scratch volume,” that’s the field.
emptyDir is also the standard fix for a read-only root filesystem. When a container runs with readOnlyRootFilesystem: true (a hardening pattern from the SecurityContext guide) but the app still needs to write to /tmp, you mount an emptyDir there.
hostPath: Know It, Rarely Use It
hostPath mounts a file or directory from the node’s filesystem into the Pod. It’s occasionally referenced on the exam but should raise a flag: it ties a Pod to a specific node, breaks portability, and is a security risk. Know what it is, but for durable app storage the answer is almost always a PersistentVolumeClaim, not hostPath.
Persistent Storage: PV, PVC, and the Claim Model
For data that must outlive the Pod, Kubernetes separates what the cluster offers from what the app requests:
| Object | Owned by | Answers |
|---|---|---|
| PersistentVolume (PV) | Cluster admin / provisioner | ”Here is a piece of storage that exists.” |
| PersistentVolumeClaim (PVC) | Application developer | ”My app needs 2Gi of ReadWriteOnce storage.” |
| StorageClass | Cluster admin | ”Here’s how to provision storage on demand.” |
As a CKAD candidate you live mostly in the PVC world — you write a claim, and the cluster binds it to a matching PV (or provisions one dynamically via a StorageClass). The Pod then references the claim, never the PV directly.
A complete example — claim, then consume:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-pvc
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
storageClassName: standard # omit to use the default StorageClass
---
apiVersion: v1
kind: Pod
metadata:
name: app
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: data-pvc # reference the claim, not a PV
Check binding status the way an examiner would:
kubectl get pvc # STATUS should be Bound
kubectl get pv # see the PV it bound to
kubectl describe pvc data-pvc # events explain why it's Pending
A PVC stuck in Pending is the number-one storage troubleshooting scenario. The usual causes: no PV matches the requested size or access mode, the named storageClassName doesn’t exist, or there’s no default StorageClass for dynamic provisioning.
Access Modes: The Most-Tested Storage Detail
Access modes describe how many nodes can mount a volume and in what mode. Memorize the four and their short forms:
| Mode | Short | Meaning |
|---|---|---|
| ReadWriteOnce | RWO | Mounted read-write by a single node |
| ReadOnlyMany | ROX | Mounted read-only by many nodes |
| ReadWriteMany | RWX | Mounted read-write by many nodes |
| ReadWriteOncePod | RWOP | Mounted read-write by a single Pod |
The trap the exam plants: ReadWriteOnce is per-node, not per-Pod. Multiple Pods on the same node can share an RWO volume; the restriction is that no second node can mount it read-write. When a scenario needs several Pods across different nodes writing to shared storage, only ReadWriteMany works — and not every storage backend supports it. ReadWriteOncePod (RWOP) is the stricter guarantee: exactly one Pod, useful when you must prevent any concurrent writers.
StorageClasses and Dynamic Provisioning
A StorageClass lets the cluster provision a PV automatically when a PVC asks for it, instead of an admin pre-creating volumes. For CKAD you mostly need to know how a claim selects one:
- Set
storageClassName: fastin the PVC to request a specific class. - Omit
storageClassNameand the PVC uses the cluster’s default StorageClass (marked with the annotationstorageclass.kubernetes.io/is-default-class: "true"). - Set
storageClassName: ""(empty string) to disable dynamic provisioning and force binding to a pre-existing PV only.
kubectl get storageclass # the one marked (default) is used when you omit the field
That empty-string-versus-omitted distinction is subtle and exam-worthy: omitting the field means “use the default class,” while explicitly setting it to "" means “don’t use any class — bind to a static PV.”
Mounting ConfigMaps and Secrets as Volumes
Config isn’t only injected as environment variables — it’s frequently mounted as files, and this falls squarely under volumes. Each key in a ConfigMap or Secret becomes a file whose contents are the value.
volumes:
- name: config
configMap:
name: app-config
- name: creds
secret:
secretName: app-secret
containers:
- name: app
image: nginx
volumeMounts:
- name: config
mountPath: /etc/app # each key becomes a file here
- name: creds
mountPath: /etc/secret
readOnly: true
A key advantage over environment variables: mounted ConfigMaps and Secrets update in place when the source object changes (after a short kubelet sync delay), whereas env-var values are fixed at container start. If a scenario needs config that refreshes without a Pod restart, mount it as a volume. For the full treatment of creating and consuming these objects, see the ConfigMaps and Secrets guide.
subPath: Mount a Single File Without Hiding the Directory
By default, mounting a volume at a path replaces whatever was in that directory. When you only want to drop a single file into an existing directory — say, one config file into /etc/nginx/ without wiping the rest — use subPath:
volumeMounts:
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf # mount just this key as a file
The catch the exam may test: a subPath mount does not receive automatic updates when the ConfigMap changes — it’s a point-in-time copy. If you need live updates, mount the whole directory instead.
Ephemeral vs. Persistent: The Decision
| Scenario cue | Use |
|---|---|
| Temporary scratch / cache | emptyDir |
| Share files between containers in one Pod | emptyDir |
| In-memory (tmpfs) scratch | emptyDir with medium: Memory |
| Writable path with read-only root filesystem | emptyDir |
| Data must survive Pod deletion/rescheduling | PersistentVolumeClaim |
| Many Pods across nodes writing shared data | PVC with ReadWriteMany |
| Inject config files that can refresh | configMap / secret volume |
| Drop one file into an existing directory | volume with subPath |
Fast Imperative Moves
You can’t create a full volume spec with a single kubectl flag, so the fast path is to generate a skeleton and edit it — the same muscle memory that powers most of the exam (keep the CKAD kubectl cheat sheet handy):
# Skeleton a Pod, then add volumes/volumeMounts by hand
kubectl run app --image=nginx --dry-run=client -o yaml > pod.yaml
# Create a PVC quickly — there's no imperative generator, so keep a template
kubectl apply -f pvc.yaml
kubectl get pvc app-pvc -w # watch it move to Bound
Because there’s no kubectl create pvc shortcut, having the PVC and volume YAML memorized (or in your notes) is one of the highest-leverage things you can drill for this domain.
Troubleshooting Playbook
Three signatures cover most storage failures on the exam:
- PVC stuck in
Pending— no matching PV, a nonexistentstorageClassName, or no default StorageClass. Runkubectl describe pvcand read the events. - Pod stuck in
ContainerCreatingwith a mount error — the referenced PVC isn’tBound, or an access-mode conflict (e.g., an RWO volume already mounted on another node). - “Permission denied” writing to a mounted volume — a filesystem-ownership problem; set
fsGroupin the Pod’ssecurityContextso the volume is group-owned by the process, as covered in the SecurityContext and ServiceAccounts guide.
For the broader describe → logs → events diagnostic loop these plug into, the CKAD application troubleshooting guide walks the full workflow.
How This Connects to the Rest of the Exam
Volumes rarely stand alone. An emptyDir is the shared channel in multi-container Pod patterns; mounted ConfigMaps and Secrets are the file-based half of application configuration; fsGroup ties storage to SecurityContext; and PVC requests interact with the resource requests and limits you set on the Pod. Seeing storage as one thread woven through the syllabus is what turns a 66% into an 85%. For the full map of what’s tested and how domains weigh against each other, start with the CKAD exam domains breakdown.
Practice Until It’s Reflex
Reading volume YAML is not the same as writing it against a two-hour clock in a live terminal. The CKAD is a performance exam — you’re graded on working objects, not multiple-choice recall — so the only preparation that transfers is repetition in a real cluster. Build a Pod with an emptyDir shared by two containers. Write a PVC, watch it bind, and mount it into an app. Mount a ConfigMap with subPath and confirm the surrounding files survive. Do each three times and the pattern sticks.
If you want that repetition under realistic exam conditions — a browser-based terminal, timed scenarios, and detailed explanations for every task — the Sailor.sh CKAD Certification Ready Mock Exam Bundle runs five full performance labs covering storage, configuration, and the rest of the syllabus on a live Kubernetes cluster. It’s designed as hands-on practice, not a question dump, so you finish knowing you can do the tasks, not just recognize them. Warm up with the free CKAD practice guide before committing to a full mock.
Frequently Asked Questions
What is the difference between an emptyDir and a PersistentVolumeClaim?
An emptyDir is an ephemeral volume that’s created empty when the Pod starts and deleted permanently when the Pod is removed from the node — good for scratch space and sharing files between containers in the same Pod. A PersistentVolumeClaim requests durable storage that outlives the Pod; the data survives Pod deletion and rescheduling because it’s backed by a PersistentVolume. Use emptyDir for temporary data and a PVC for anything that must persist.
How do I share files between two containers in the same Pod?
Declare an emptyDir volume at the Pod level and mount it into both containers with volumeMounts, using the same volume name but any paths you like. Both containers see the same underlying directory, so one can write files the other reads — the standard sidecar pattern.
What does a PVC in Pending status mean?
The claim hasn’t been bound to a PersistentVolume. Common causes: no existing PV matches the requested size or access mode, the storageClassName you specified doesn’t exist, or there’s no default StorageClass to dynamically provision one. Run kubectl describe pvc <name> and read the events to find the exact reason.
What is the difference between ReadWriteOnce and ReadWriteMany?
ReadWriteOnce (RWO) allows read-write mounting by a single node — multiple Pods on that same node can share it, but no second node can mount it read-write. ReadWriteMany (RWX) allows read-write mounting by many nodes simultaneously, which is required when Pods spread across different nodes must all write to the same volume. Not every storage backend supports RWX.
How do I mount a ConfigMap as a file instead of an environment variable?
Declare a configMap volume referencing the ConfigMap, then mount it into the container with volumeMounts at a directory path — each key becomes a file whose contents are the value. Mounted ConfigMaps update in place when the source changes (unlike environment variables), unless you used subPath, which is a point-in-time copy that doesn’t auto-update.
Is storage heavily tested on the CKAD exam?
Yes. Volumes fall under “Application Design and Build” (about 20% of the exam) via the “persistent and ephemeral volumes” objective, and they also appear inside multi-container, configuration, and troubleshooting tasks. Because the skills are mechanical — declare a volume, mount it, request a PVC — they’re high-value points you can lock in by drilling the YAML until you can write it from memory.