Ephemeral volumes
Kubernetes has two families of storage, and they differ in one question: what does the data’s lifetime belong to?
An ephemeral volume belongs to the Pod. It is created when the Pod starts on a node, and it is gone when the Pod is. A persistent volume — the next page — belongs to the cluster and outlives everything.
Ephemeral volumes are not a lesser thing. They are exactly right for scratch space, for sharing files between containers in a Pod, and for injecting configuration. Knowing when you do not need durable storage saves a lot of unnecessary complexity.
Reference
- https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/
- https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
Terminology
emptyDir— An initially empty directory, created on the node when the Pod is scheduled, shared by every container in the Pod. The workhorse of ephemeral storage.emptyDir.medium: Memory— The same thing, backed bytmpfsinstead of disk. Fast, and the size counts against the container’s memory limit.sizeLimit— A cap on anemptyDir. Exceeding it does not fail the write; it gets the Pod evicted.- Injected volumes —
configMap,secret,downwardAPIandprojectedvolumes. Kubernetes writes data into a directory for you. Read-only by nature. ephemeralvolume type — A generic ephemeral volume: a real PVC created automatically per Pod, and deleted with it. For when you want a proper disk with the Pod’s lifetime, and no more.
First, A Distinction People Miss
A container’s own writable layer is not a volume. It is per-container, and it is destroyed every time that container restarts.
An emptyDir is per-Pod, and it survives container restarts. That difference is the whole reason emptyDir exists:
| What happens | Container’s own filesystem | emptyDir |
|---|---|---|
| A process crashes and the container restarts | Gone | Survives |
| The Pod is deleted | Gone | Gone |
| The Pod is rescheduled onto another node | Gone | Gone |
| The container is replaced by a rolling update | Gone | Gone |
So: an emptyDir is not durable, but it is more durable than the container — which is exactly what you want for a scratch directory or a file a sidecar is tailing.
The Classic Use: Two Containers, One Directory
apiVersion: v1
kind: Pod
metadata:
name: shared
spec:
containers:
- name: writer
image: docker.io/library/busybox:1.36
command: ["sh", "-c", "while true; do date >> /scratch/log.txt; sleep 2; done"]
volumeMounts:
- name: scratch
mountPath: /scratch
- name: reader
image: docker.io/library/busybox:1.36
command: ["sh", "-c", "tail -f /scratch/log.txt"]
volumeMounts:
- name: scratch
mountPath: /scratch
volumes:
- name: scratch
emptyDir: {}This is the shape of a log shipper, a config reloader, a cache warmer — anything where one container produces and another consumes. emptyDir is what makes it possible without either container knowing about the other.
Exercises
Share a directory between two containers. Apply the Pod above, then read the same file from both:
kubectl exec shared -c writer -- cat /scratch/log.txt kubectl exec shared -c reader -- cat /scratch/log.txtNote that the two containers have separate filesystems everywhere except the mount you gave them.
ls /in each looks different;/scratchis the same.Prove it survives a container restart, and not a Pod restart. Kill the writer’s main process and watch it come back with the file intact:
kubectl exec shared -c writer -- sh -c "echo 'before the restart' >> /scratch/log.txt" kubectl exec shared -c writer -- kill 1 kubectl get pod shared # RESTARTS has incremented kubectl exec shared -c writer -- head -1 /scratch/log.txt # still thereNow delete the Pod and create it again. The directory is empty: it was never durable, only longer-lived than the container.
Use memory instead of disk. Set
medium: Memoryand look at it from inside:volumes: - name: cache emptyDir: medium: Memory sizeLimit: 64Mikubectl exec <pod> -- df -h /cacheThe filesystem is
tmpfs, and its size is bounded bysizeLimitand by the container’s memory limit — whichever is smaller. This is the trap: atmpfsemptyDiris memory, so writing 100 MiB into it with a 128 MiB memory limit is how you create a Pod that getsOOMKilledwhile doing something that looks like file I/O.Exceed
sizeLimitand watch the consequence. Write more than the limit into anemptyDirwithsizeLimitset, and check the Pod:kubectl exec <pod> -- sh -c "dd if=/dev/zero of=/cache/big bs=1M count=200" kubectl get pod <pod> # Evicted kubectl describe pod <pod> | tail -20The write does not fail cleanly. The kubelet notices and evicts the Pod, which is why
sizeLimitis a safety net rather than a quota.Give a hardened container somewhere to write. A container with
readOnlyRootFilesystem: truecannot write to/tmpor its own cache directory. The fix is anemptyDir:containers: - name: app securityContext: readOnlyRootFilesystem: true # container-level, not Pod-level volumeMounts: - name: tmp mountPath: /tmp - name: cache mountPath: /var/cache/nginxThis is the standard pattern for hardened workloads: an immutable root filesystem plus explicit, small writable mounts — which also documents precisely what the application writes.
Inject configuration as a volume. A
configMaporsecretvolume is read-only and, mounted as a directory, updates when the source changes. Compare that with consuming the same ConfigMap as environment variables, which never updates — see ConfigMaps.
Gotchas Worth Knowing
- An
emptyDirlives on one node. Reschedule the Pod and you get an empty one. It is not shared storage and never was. medium: Memorycounts as memory, against the container’s limit and the node’s capacity. It is fast and it is not free.sizeLimitevicts rather than rejects. There is no “disk full” error for the application to handle.subPathmounts never update, which matters for injected ConfigMap and Secret volumes: mount the whole volume if you want the contents to change underneath a running container.- Permissions follow the Pod’s
securityContext. A non-root container needsfsGroupor a writable mount, exactly as with a persistent volume — see Security context.
What to Take Away
- Ephemeral storage belongs to the Pod; if you need it to outlive the Pod, this is the wrong tool.
emptyDirsurvives container restarts, which is why it is the right home for scratch data and sidecar handoffs.medium: Memoryis memory, with all that implies about limits and eviction.- A
readOnlyRootFilesystemplus a couple of smallemptyDirmounts is both more secure and a clearer statement of what a container needs to write.