Docker & ContainersVIII Β· Storagetmpfs
tmpfs mounts β ephemeral, fast storage
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
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-onlyplus a tmpfs at/tmpand/runis 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
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"$ grep -E '^(anon|file|shmem) ' $CG/memory.stat; cat $CG/memory.current $CG/memory.max; cat $CG/memory.eventsanon 31817728
file 230162432
shmem 0
current: 263929856
max: 536870912
low 0
high 0
max 0
oom 0
oom_kill 0
oom_group_kill 0Read 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 | |
|---|---|---|
| Size | size=512m | tmpfs-size=536870912 (bytes) |
| Mode | mode=1777 | tmpfs-mode=1777 (default 1777) |
noexec, nosuid, nodev | Supported | Not available |
uid, gid, nr_inodes | Supported | Not available |
| Swarm services | Not supported | Supported |
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.
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:
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
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
| Feature | tmpfs | Volume |
|---|---|---|
| Backing | RAM (and swap, unless disabled) | Host disk |
Counts against --memory | Yes | No |
| Durability | None; gone with the container | Survives container removal |
| Survives reboot | No | Yes |
| Shared between containers | No | Yes |
| Resize while running | No | N/A |
| Failure when full | ENOSPC, or OOM kill if unsized | ENOSPC |
Knowledge check
Knowledge check Β· 5 questions
Q1. A container runs with `--memory 512m --tmpfs /tmp` on a 64 GB host. The process writes 600 MB to /tmp. What happens?
Q2. Which cgroup v2 `memory.stat` field distinguishes a container filling a tmpfs from one leaking heap?
Q3. Which options are available with `--tmpfs` but NOT with `--mount type=tmpfs`? Select all that apply.
Q4. On a host with swap enabled, the kernel can write tmpfs pages out to the swap device under memory pressure.
Q5. A SIZED tmpfs fills up. What is the correct expectation?
Passing score: 75%. Answers are checked in this browser.