Skip to content
Ephemeral volumes

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

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 by tmpfs instead of disk. Fast, and the size counts against the container’s memory limit.
  • sizeLimit — A cap on an emptyDir. Exceeding it does not fail the write; it gets the Pod evicted.
  • Injected volumesconfigMap, secret, downwardAPI and projected volumes. Kubernetes writes data into a directory for you. Read-only by nature.
  • ephemeral volume 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 happensContainer’s own filesystememptyDir
A process crashes and the container restartsGoneSurvives
The Pod is deletedGoneGone
The Pod is rescheduled onto another nodeGoneGone
The container is replaced by a rolling updateGoneGone

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

  1. 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.txt

    Note that the two containers have separate filesystems everywhere except the mount you gave them. ls / in each looks different; /scratch is the same.

  2. 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 there

    Now delete the Pod and create it again. The directory is empty: it was never durable, only longer-lived than the container.

  3. Use memory instead of disk. Set medium: Memory and look at it from inside:

    volumes:
      - name: cache
        emptyDir:
          medium: Memory
          sizeLimit: 64Mi
    kubectl exec <pod> -- df -h /cache

    The filesystem is tmpfs, and its size is bounded by sizeLimit and by the container’s memory limit — whichever is smaller. This is the trap: a tmpfs emptyDir is memory, so writing 100 MiB into it with a 128 MiB memory limit is how you create a Pod that gets OOMKilled while doing something that looks like file I/O.

  4. Exceed sizeLimit and watch the consequence. Write more than the limit into an emptyDir with sizeLimit set, 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 -20

    The write does not fail cleanly. The kubelet notices and evicts the Pod, which is why sizeLimit is a safety net rather than a quota.

  5. Give a hardened container somewhere to write. A container with readOnlyRootFilesystem: true cannot write to /tmp or its own cache directory. The fix is an emptyDir:

    containers:
      - name: app
        securityContext:
          readOnlyRootFilesystem: true     # container-level, not Pod-level
        volumeMounts:
          - name: tmp
            mountPath: /tmp
          - name: cache
            mountPath: /var/cache/nginx

    This is the standard pattern for hardened workloads: an immutable root filesystem plus explicit, small writable mounts — which also documents precisely what the application writes.

  6. Inject configuration as a volume. A configMap or secret volume 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 emptyDir lives on one node. Reschedule the Pod and you get an empty one. It is not shared storage and never was.
  • medium: Memory counts as memory, against the container’s limit and the node’s capacity. It is fast and it is not free.
  • sizeLimit evicts rather than rejects. There is no “disk full” error for the application to handle.
  • subPath mounts 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 needs fsGroup or 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.
  • emptyDir survives container restarts, which is why it is the right home for scratch data and sidecar handoffs.
  • medium: Memory is memory, with all that implies about limits and eviction.
  • A readOnlyRootFilesystem plus a couple of small emptyDir mounts is both more secure and a clearer statement of what a container needs to write.