Kubernetes - emptyDir Volumes: Sharing, Lifecycle, and Memory-Backed Storage
An emptyDir volume defined in a pod is created when the pod is assigned to a node. All containers in the pod can mount it and share the same files. The volume's lifecycle matches the pod's: deleting the pod removes the volume and its contents.
The default medium for emptyDir is the node's disk - specifically, the filesystem under the kubelet's working directory (usually /var/lib/kubelet, configurable via --root-dir). The first three tasks use this default. Tasks 4, 5, and 6 switch to medium: Memory and explore its behavior.
On the node, each emptyDir volume lives at /var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~empty-dir/<volume-name>/. Task 6 uses this path to recover a pod stuck in a restart loop after the OOM.
Task 1 - Share Data Between Containers
Two containers in the same pod can exchange data through a shared emptyDir. One container writes, the other reads. No network, no external storage.
Steps:
- Create a pod named
sharedwith two containerswriterandreader, both using imagebusybox - Define one
emptyDirvolume namedshared-dataat the pod level - Mount the volume at
/sharedin both containers writerrunsecho 'hello from writer' > /shared/data.txt && sleep 3600readerrunssleep 3 && cat /shared/data.txt && sleep 3600
kubectl logs shared -c reader
Hint: shared volume between two containers
Define one volume at the pod level, then list it under volumeMounts in both containers with the same mountPath.
spec:
volumes:
- name: shared-data
emptyDir: {}
containers:
- name: writer
...
volumeMounts:
- name: shared-data
mountPath: /shared
- name: reader
...
volumeMounts:
- name: shared-data
mountPath: /shared
Task 2 - Volume Survives Container Crash
emptyDir is tied to the pod, not to the container. When a container crashes and the kubelet restarts it, the volume is still there with the same data. When the pod is deleted, the volume is gone.
To trigger a controlled container restart from the CLI, the pod uses a livenessProbe that checks for the absence of /tmp/die. Creating the file makes the probe fail and the kubelet restarts the container. Because /tmp lives in the container's writable layer, it is wiped on restart and the probe passes again. The mounted emptyDir at /data keeps its contents through the restart.
Steps:
- Create a pod named
crash-testwith one container namedapp, using imagebusybox, running commandsleep 3600 - Define one
emptyDirvolume nameddataat the pod level - Mount the volume at
/datain the container - Add a
livenessProbethat runstest ! -f /tmp/dieevery 1 second (the probe fails when the file exists) - Write a non-empty file to
/data/message.txtinside the container usingkubectl exec - Create
/tmp/dieinside the container to make the probe fail - Verify
restartCountis at least1and/data/message.txtstill exists
kubectl get pod crash-test
kubectl exec crash-test -- cat /data/message.txt
The file persists across the container restart. The pod's restartCount increments while the contents of /data remain unchanged.
After creating /tmp/die, the restart can take a few seconds. The kubelet has to stop the old container before starting a new one, and the default wait for that is 30 seconds. To speed it up, set .spec.terminationGracePeriodSeconds: 5 on the pod.
To see the other side - delete crash-test and create a new pod with the same spec. The /data directory will be empty. A new pod always gets a fresh emptyDir.
Hint: kubectl exec to write into a running container
kubectl exec runs a command inside an existing container. Use it to write directly into the mounted volume without modifying the pod spec.
kubectl exec crash-test -- sh -c 'echo hello > /data/message.txt'
Hint: livenessProbe shape
The probe runs test ! -f /tmp/die. The command exits 0 when the file is absent (probe passes) and 1 when the file is present (probe fails). After enough consecutive failures the kubelet restarts the container.
livenessProbe:
exec:
command: ["sh", "-c", "test ! -f /tmp/die"]
periodSeconds: 1
failureThreshold: 1
Hint: trigger the restart
Create /tmp/die inside the running container. Within the probe's next period, the probe fails and the kubelet restarts the container.
kubectl exec crash-test -- touch /tmp/die
kubectl get pod crash-test -w
Watch RESTARTS go from 0 to 1, then Ctrl+C.
Hint: check restartCount
kubectl get with jsonpath extracts specific fields from the pod object. This returns the same restartCount value visible in kubectl describe, but as a single value suitable for scripting.
kubectl get pod crash-test -o jsonpath='{.status.containerStatuses[*].restartCount}'
Task 3 - Init Container Populates the Volume
An init container runs to completion before the main container starts. Mounting a shared emptyDir between them is a common pattern for pulling configs, rendering templates, or running setup scripts before the app runs.
Steps:
- Create a pod named
init-demowith an init container namedsetupand a main container namedapp, both using imagebusybox - Define one
emptyDirvolume namedconfigat the pod level and mount it at/configin both containers setuprunsecho 'config loaded by init container' > /config/app.confapprunscat /config/app.conf && sleep 3600
kubectl logs init-demo -c app
Hint: init container with shared volume
The init container and the main container mount the same emptyDir. The init container writes the file and exits, then the main container starts and reads it.
spec:
initContainers:
- name: setup
...
volumeMounts:
- name: config
mountPath: /config
containers:
- name: app
...
volumeMounts:
- name: config
mountPath: /config
Task 4 - Memory-Backed emptyDir
Setting medium: Memory on an emptyDir makes it a tmpfs mount. Reads and writes go to RAM instead of disk, which is faster than a regular emptyDir. The storage counts against the container's memory limit. Pick medium: Memory only when the data is small and latency matters. Filling the volume past the memory limit will OOM-kill the container.
Steps:
- Create a pod named
mem-podwith one container namedapp, using imagebusybox, running commandsleep 3600 - Define one
emptyDirvolume namedmemat the pod level withmedium: Memory - Mount the volume at
/memin the container - Verify the mount is
tmpfs
kubectl exec mem-pod -- mount | grep /mem
Hint: medium: Memory
volumes:
- name: mem
emptyDir:
medium: Memory
Task 5 - OOM-kill from a Memory-Backed emptyDir
The tmpfs storage for a medium: Memory emptyDir counts against the container's memory limit. Writing past that limit triggers the kernel OOM-killer, which terminates the container's main process. The kubelet then restarts the container, recording an OOMKilled event on the pod.
The OOMKilled reason is visible in kubectl describe pod (in the Events section and in the container state). The current pod status may show another reason like StartError or CrashLoopBackOff because the tmpfs file remains in the volume after the first OOM, and the restarted container often dies again for related reasons.
Steps:
- Create a pod named
oom-testwith one container namedapp, imagebusybox, running commandsleep 3600 - Set
resources.limits.memory: 50Mion the container - Add an
emptyDirvolume namedmemwithmedium: Memory, mounted at/mem(nosizeLimit) - Use
kubectl execto write 100Mi into/mem/bigwithdd if=/dev/zero of=/mem/big bs=1M count=100 - Verify the container was OOM-killed and restarted
kubectl get pod oom-test
kubectl describe pod oom-test
The pod shows RESTARTS greater than 0. The describe output contains OOMKilled in either the container's Last State section or in the Events list. The currently visible status in kubectl get pod may be Error, StartError, or CrashLoopBackOff.
Setting sizeLimit on the emptyDir to a value smaller than the container's memory limit changes the failure mode. The write fails with No space left on device before the container exceeds its memory limit. The container stays Running and no OOM-kill occurs.
Hint: container memory limit
resources.limits.memory sets the upper bound on the container's memory cgroup. The kernel OOM-killer fires when the cgroup exceeds this value.
resources:
limits:
memory: 50Mi
Hint: fill the tmpfs with dd
dd reads zeros from /dev/zero and writes them to the target file. With bs=1M count=100, it writes 100Mi.
kubectl exec oom-test -- dd if=/dev/zero of=/mem/big bs=1M count=100
The command may fail with an error or the pod may restart mid-write. Either outcome is fine. kubectl describe pod oom-test shows the OOM-kill in the container's Last State section or in the Events list.
Task 6 - Restore the OOM-Crashed Pod
After the OOM, the container keeps restarting because /mem/big is still in the tmpfs volume across container restarts. Each new container hits the memory limit again. The fix is to remove the file from the node side so the next restart can succeed.
Steps:
- Find the pod's UID
- On
cplane-01, navigate to the kubelet'semptyDirdirectory for this pod (see the intro for the path layout) - Locate and delete the
bigfile from thememvolume - Wait for the pod to return to
Running
The pod may take up to 5 minutes to return to Running. The kubelet's restart backoff doubles after each crash (10s, 20s, 40s, 80s, 160s, 300s) and caps at 5 minutes. If you delete the file right after a fresh crash, the wait can be just a few seconds. If you delete it after many restarts, the wait is closer to the 5-minute cap.
Hint: get the pod UID
kubectl get pod oom-test -o jsonpath='{.metadata.uid}'
Hint: open a root shell on the node
The kubelet's pod directory is owned by root with mode 700. Become root before walking into it.
sudo -i
Hint: walk into the volume on the node
cd /var/lib/kubelet/pods/<pod-uid>/volumes/kubernetes.io~empty-dir/
ls
cd mem
ls
Hint: remove the file
rm big
exit