Skip to main content
RunBook Academy

Docker & ContainersVIII Β· Storagetmpfs

tmpfs mounts β€” ephemeral, fast storage

Intermediate⏱ ~20 min

What you'll learn

  • Use tmpfs mounts for ephemeral, fast storage
  • Size a tmpfs so it cannot exhaust the container memory limit
  • Choose between --tmpfs and --mount type=tmpfs and know what each cannot do
  • Pair tmpfs with a read-only root filesystem

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-12

Not yet marked complete on this device.

tmpfs is a RAM-backed filesystem. Files written to tmpfs live in memory, and when the container stops the tmpfs is gone β€” not unmounted, gone. There is nothing on disk to clean up because there was never anything on disk.

That is the whole appeal, and it is also where both of its failure modes come from.

When tmpfs is right

  • Performance-critical scratch space. Build intermediates, caches, compilation output, transcoding buffers. Anything that benefits from fast I/O and does not need to survive.
  • A writable path in a read-only container. The main modern use. --read-only plus a tmpfs at /tmp and /run is the standard hardening pattern.
  • Short-lived secret material. A fetched token that should not land in a layer or a volume β€” with the swap caveat below.

When tmpfs is wrong

  • Durable state. A database on tmpfs loses everything when the container stops. This is not a warning about crashes; it is the designed behaviour.
  • Anything larger than you have modelled. tmpfs pages are container memory, as the next section explains.
  • Sharing. tmpfs mounts cannot be shared between containers, and they are only available on Linux.

The failure mode: tmpfs is charged to your memory limit

Read-only / Safetmpfs accounting
CONTAINER=web
CID=$(docker inspect --format '{{.Id}}' "$CONTAINER")
CG=/sys/fs/cgroup/system.slice/docker-$CID.scope

grep -E '^(anon|file|shmem) ' "$CG/memory.stat"
echo "current: $(cat "$CG/memory.current")"
echo "max:     $(cat "$CG/memory.max")"
echo '--- has it been killed before? ---'
cat "$CG/memory.events"
Read-only / Safea healthy container
$ grep -E '^(anon|file|shmem) ' $CG/memory.stat; cat $CG/memory.current $CG/memory.max; cat $CG/memory.events
anon 31817728
file 230162432
shmem 0
current: 263929856
max: 536870912
low 0
high 0
max 0
oom 0
oom_kill 0
oom_group_kill 0

Read that as: 30 MB of heap, 230 MB of page cache the kernel can reclaim under pressure, nothing in tmpfs, 251 MB charged of a 512 MiB limit, never killed. If shmem were 400 MB and oom_kill were 3, you would have your answer in one command.

Sizing

Always size a tmpfs. There is no situation where inheriting half the host’s RAM is what you meant.

# Sized, and no smaller than the container can tolerate losing
docker run -d --name build \
  --memory 2g \
  --tmpfs /tmp:rw,noexec,nosuid,nodev,size=512m,mode=1777 \
  myorg/build-tools:1

Budget it explicitly: size plus the application’s expected heap must fit inside --memory, with headroom. A 2 GB limit with a 512 MB tmpfs leaves the process 1.5 GB, and a process that needs 1.4 GB has 100 MB of margin for everything else in the container.

In Compose, note that tmpfs: and the tmpfs: key under a long-form volume entry are different things:

services:
  build:
    image: myorg/build-tools:1
    mem_limit: 2g
    tmpfs:
      - /tmp:rw,noexec,nosuid,nodev,size=512m

Beyond the limit, writes fail with ENOSPC β€” the correct, diagnosable failure. That is what sizing buys you: a full filesystem instead of a dead container.

--tmpfs versus --mount type=tmpfs

They are not interchangeable, and the gap runs the opposite way from bind mounts.

--tmpfs--mount type=tmpfs
Sizesize=512mtmpfs-size=536870912 (bytes)
Modemode=1777tmpfs-mode=1777 (default 1777)
noexec, nosuid, nodevSupportedNot available
uid, gid, nr_inodesSupportedNot available
Swarm servicesNot supportedSupported

So the hardened form and the Swarm-compatible form are mutually exclusive on the CLI. If you are hardening a docker run or a Compose deployment, use --tmpfs and set noexec,nosuid,nodev explicitly β€” they are the options that stop a writable world-mode directory from being a place to drop and run a binary.

The read-only root pattern

This is where tmpfs earns its place in production rather than in builds.

Read-only / Saferead-only root with scratch
docker run -d --name api --read-only --memory 1g --tmpfs /tmp:rw,noexec,nosuid,nodev,size=64m --tmpfs /run:rw,noexec,nosuid,nodev,size=16m,mode=0755 --security-opt no-new-privileges myorg/api:1.4.0

--read-only alone breaks most images, because something writes a PID file, a socket, or a temp file during startup. The tmpfs mounts give those writes somewhere to go without giving anything a durable foothold in the container filesystem. noexec on both means a downloaded payload cannot be executed from the one writable place in the container.

Find out what actually needs to be writable rather than guessing:

Read-only / Safewhat does it write
CONTAINER=api
docker diff "$CONTAINER"

docker diff lists every path added (A), changed (C) or deleted (D) in the writable layer since start. Each A or C outside a volume is a path that needs a tmpfs before --read-only will work.

Verification that can fail

Read-only / Safeverify the mount
CONTAINER=api

# Type and size as the kernel recorded them, not as you intended
docker exec "$CONTAINER" df -hT /tmp

# The mount options actually in force. Expect noexec, nosuid, nodev, size.
docker exec "$CONTAINER" grep ' /tmp ' /proc/self/mounts

# noexec really applies: this must FAIL
docker exec "$CONTAINER" sh -c 'cp /bin/true /tmp/x && chmod +x /tmp/x && /tmp/x' && echo 'EXECUTABLE - noexec is missing' || echo 'noexec enforced'

The last check is the one worth automating. noexec silently disappears the moment someone converts the flag to --mount type=tmpfs, which does not support it, and nothing about the container looks different afterwards.

How tmpfs differs from volumes

FeaturetmpfsVolume
BackingRAM (and swap, unless disabled)Host disk
Counts against --memoryYesNo
DurabilityNone; gone with the containerSurvives container removal
Survives rebootNoYes
Shared between containersNoYes
Resize while runningNoN/A
Failure when fullENOSPC, or OOM kill if unsizedENOSPC

Knowledge check

Knowledge check Β· 5 questions

  1. Q1. A container runs with `--memory 512m --tmpfs /tmp` on a 64 GB host. The process writes 600 MB to /tmp. What happens?

  2. Q2. Which cgroup v2 `memory.stat` field distinguishes a container filling a tmpfs from one leaking heap?

  3. Q3. Which options are available with `--tmpfs` but NOT with `--mount type=tmpfs`? Select all that apply.

  4. Q4. On a host with swap enabled, the kernel can write tmpfs pages out to the swap device under memory pressure.

  5. Q5. A SIZED tmpfs fills up. What is the correct expectation?

Passing score: 75%. Answers are checked in this browser.