overlay2 Internals: How Docker Really Stores and Runs Your Containers

overlay2 Internals: How Docker Really Stores and Runs Your Containers

A deep, from-first-principles walkthrough of Docker's default storage driver — how layers are built, how writes are captured, why some images boot in milliseconds, and where the design quietly breaks down.

Imagine a photocopier that never actually copies the original page. Instead, every time someone wants to “edit” a document, the copier hands them a transparent sheet to scribble on, and stacks that sheet on top of the original. Anyone reading the document sees the original page with the scribbles on top — but the original itself is never touched, so a hundred people can each get their own transparent sheet over the exact same original, without a hundred photocopies being made. That transparent-sheet trick is, in essence, what Docker’s overlay2 storage driver does with your filesystem, and understanding it is the key to understanding why containers start in milliseconds, why images share space on disk, and why a container’s writes vanish the moment you delete it. By the end of this tutorial, the goal isn’t just to recognize the name “overlay2” — it’s to be able to look at any container storage symptom, from a mysteriously full disk to a slow database write, and reason directly back to the exact mechanism causing it.

1What overlay2 Actually Is

Before touching any internals, it helps to place overlay2 correctly: it is not Docker, it is not a filesystem you format a disk with, and it is not unique to Docker.

The one-sentence definition

overlay2 is Docker’s default storage driver on Linux — a piece of software that tells the Docker engine how to store image layers and container filesystems on disk using the Linux kernel’s built-in OverlayFS filesystem. It is not something you install separately; it has shipped inside the Linux kernel since version 3.18 (merged, stable form arrived around 4.0+), and Docker simply calls into it through kernel mount syscalls.

Simple Analogy

Think of overlay2 as the “sticky note” system for a shared textbook. The textbook (a Docker image) is printed once and never altered. When you (a container) want to write in the margins, you don’t get your own printed copy — you get a stack of transparent sticky notes placed over the pages. You can write, cross out, or “hide” printed text using your sticky notes, but the underlying textbook page is untouched, so your friend reading the same textbook with a different sticky-note stack never sees your notes.

Kernel Feature

OverlayFS

The actual filesystem implementation living inside the Linux kernel that merges multiple directories into one unified view.

Docker Component

overlay2 driver

Docker’s storage-driver code that decides directory layout, naming, and how images/containers map onto OverlayFS mounts.

Predecessor

overlay (legacy)

An older, less efficient driver superseded by overlay2 once the kernel gained multiple lower-directory support.

Siblings

aufs, devicemapper, btrfs, zfs, fuse-overlayfs

Alternative storage drivers Docker also supports, each with different trade-offs covered later in this tutorial.

i
Why “2”?

The original overlay driver could only combine exactly two directories (one lower, one upper). Kernel 4.0 added support for multiple lower directories, which is what makes efficient, multi-layer image sharing possible — Docker’s driver for that capability is called overlay2 to distinguish it from the original.

A brief history, in plain terms

OverlayFS itself started life as an out-of-tree kernel patch used by early Linux live-CD and embedded-systems projects, long before containers were mainstream — those projects needed a cheap way to let a read-only base system appear “writable” during a live session. Docker’s earliest releases actually shipped with the older, patch-dependent aufs driver instead, because at the time OverlayFS wasn’t yet merged into mainline Linux. Once OverlayFS landed in the mainline kernel and later gained multi-lowerdir support, Docker migrated its default to overlay2, and the ecosystem has broadly stayed there ever since.

3.18
Kernel version OverlayFS was first merged into mainline Linux
4.0+
Kernel version that added multiple-lowerdir support, enabling overlay2
4
Directory roles every overlay2 mount is built from

2Core Concepts: Union Filesystems

overlay2 belongs to a family of technologies called union filesystems (or union mounts). Understanding this family explains almost everything that follows.

What is a union filesystem?

A union filesystem lets you take several separate directories, stack them logically, and present them to the operating system as one single, merged directory — without physically copying any files between them. If the same filename exists in two of the stacked directories, the topmost one “wins” and is the one users see. This merging happens live, in the kernel, every time a process reads that directory.

Simple Analogy

Picture three transparent slides on an overhead projector, each with different drawings. When stacked and projected, you see one combined picture — not three separate images. If two slides both have something drawn in the same corner, only the slide on top is visible in that spot. Union filesystems do exactly this with directories of files instead of slides.

The four directories overlay2 juggles

Every single OverlayFS mount — whether for an image layer or a running container — is built from four directory roles. Learning these four names is non-negotiable for understanding overlay2, because every log message, every troubleshooting guide, and every docker inspect output refers to them directly.

Read-only

lowerdir

One or more stacked, read-only directories — these are the image layers. Nothing here is ever modified after it’s created.

Read-write

upperdir

A single writable directory that captures every change a running container makes — new files, edits, deletions.

Combined view

merged

The unified directory a container’s process actually sees as its root filesystem — the “projected image” from our slide analogy.

Internal bookkeeping

workdir

A scratch directory the kernel uses internally to safely prepare file operations before committing them into upperdir. Applications never touch it directly.

!
Common Misconception

People often assume a running container “has its own filesystem copy.” It doesn’t. A container is really just an upperdir plus a read-only stack of lowerdirs, merged live. Nothing is duplicated until something is actually written.

How directory listings actually get merged

When a process asks to list the contents of a directory in the merged view, the kernel doesn’t just show the topmost layer’s version and stop — it walks every lowerdir that also contains a directory of the same name, at the same path, and combines all of their entries into one listing, with upperdir’s version of any given filename taking priority whenever the same name appears more than once. A directory that has never been touched by the container might therefore be assembled, live, from pieces scattered across several different image layers, all without the kernel ever writing a merged copy back to disk — the merge is computed on every access, not stored.

Simple Analogy

It’s like several people independently filling in different rows of a shared seating chart. No single sheet has the whole picture on its own, but a glance across every sheet at once — checking the topmost sheet first for any seat number — tells you exactly who is sitting where.

3Architecture & Components on Disk

All of this theory lives at a very real, very inspectable location on your machine: /var/lib/docker/overlay2.

The directory Docker actually maintains

When Docker is configured to use overlay2, every image layer and every container gets its own subdirectory under /var/lib/docker/overlay2/, named with a long random-looking hash (its layer ID). Inside each of those hash-named folders, Docker keeps the pieces OverlayFS needs.

Path (relative)RoleExists for
<layer-id>/diffThe actual file content added by this layerEvery image layer and every container
<layer-id>/linkA short symlink alias to avoid huge mount argumentsEvery layer
<layer-id>/lowerA text file listing the short names of parent layers below this oneEvery non-base layer
<layer-id>/workThe kernel’s internal workdir scratch spaceOnly container (writable) layers
<layer-id>/mergedThe unified mount point — the container’s visible root filesystemOnly running containers
l/ (top-level)A folder of short symlinks pointing back into diff foldersDocker-wide, shared by all layers
i
Why the short symlinks in l/ exist

Linux mount syscalls have a maximum argument length (typically one page, ~4KB). A container built on 20+ layers would produce a lowerdir string of dozens of full 64-character hash paths, easily blowing past that limit. Docker works around this by creating a short (~26 character) symlink for every layer inside a shared l/ directory, then builds the mount option string out of those short symlinks instead of the long paths.

graph TD
    A["/var/lib/docker/overlay2/"] --> B["l/  (short symlinks)"]
    A --> C["<layer-id-1>/diff  (base OS files)"]
    A --> D["<layer-id-2>/diff  (app dependencies)"]
    A --> E["<layer-id-3>/diff  (app code)"]
    A --> F["<container-id>/diff  (writable layer)"]
    A --> G["<container-id>/work  (kernel scratch)"]
    A --> H["<container-id>/merged  (unified view container sees)"]
    B -. "short alias" .-> C
    B -. "short alias" .-> D
    B -. "short alias" .-> E
    H --> F
    H --> E
    H --> D
    H --> C
        
FIG 1 — Physical layout of an overlay2-backed container on disk

The identity trio: RepoDigests, DiffIDs, and layer IDs

It’s worth knowing that the hash-named folder you see on disk is not the same hash you see with docker images. An image layer actually has three related identities: a content hash for the compressed layer as distributed over the network, a “DiffID” hash for the uncompressed content, and the local overlay2 cache-id used purely for the folder name on this particular machine. Docker keeps a small internal database mapping between all three so it never has to re-download or re-extract a layer it already has.

The metadata database backing all of this

Alongside the layer folders themselves, Docker’s image and layer store maintains its own small embedded database (historically BoltDB-based, under /var/lib/docker/image/overlay2/) that records, for every layer, which parent it stacks on, its DiffID, its size, and its local cache-id. This is the piece of bookkeeping that lets commands like docker history reconstruct a full, ordered layer chain for an image, and it’s exactly why manually deleting a layer folder by hand — bypassing Docker entirely — is dangerous: the database still thinks the layer exists, and a wide range of subsequent operations can fail or misbehave once the two disagree.

!
Common Mistake

Treating /var/lib/docker/overlay2/ as “just a folder you can clean up with rm -rf.” The directory tree and Docker’s internal metadata database must always agree; manual edits are one of the most common ways people accidentally corrupt a Docker installation’s image store.

4Internal Working: Copy-Up and Whiteouts

Two mechanisms — copy-up and whiteout files — do essentially all of the heavy lifting inside overlay2. Master these two and the rest of the driver’s behavior becomes predictable.

Copy-up: how “editing” a read-only file actually works

Since lowerdirs are strictly read-only, what happens when a process inside a container tries to modify a file that currently lives in a lower (image) layer? The kernel performs an operation called copy-up: it copies the entire file, unmodified, from whichever lowerdir it currently lives in, up into the container’s upperdir — and only then applies the requested change to that new copy. From that point on, every read of that file is served from the upperdir copy, because upperdir always takes priority in the merged view.

Simple Analogy

It’s like a library book you’re not allowed to write in. If you want to annotate a page, the librarian photocopies that one page for you, hands you the photocopy, and from then on you scribble on your personal photocopy while the original book on the shelf stays pristine. Anyone else who checks out the same book still sees a clean page.

!
The hidden performance cost

Copy-up operates on the whole file, not just the changed bytes. Modifying one byte inside a 2GB log file that lives in a lower layer forces the kernel to copy the entire 2GB file into upperdir first. This is one of the most common real-world overlay2 performance surprises, especially with databases or large log files stored inside the image layers instead of a mounted volume.

Whiteout files: how deletion is faked

Because lowerdirs can never be modified, overlay2 can’t literally erase a file that exists in a lower layer. Instead, when a container “deletes” such a file, the kernel creates a special marker inside upperdir called a whiteout — a character device file with device number 0/0, sharing the same name as the file being hidden. When OverlayFS builds the merged view, it sees the whiteout marker and simply omits that filename from the presented listing, even though the original still physically exists, untouched, down in the lowerdir.

Simple Analogy

It’s the sticky-note equivalent of covering a printed line with correction tape. The original printed word is still there under the tape — you haven’t erased ink from the textbook — but visually, to anyone reading through the sticky-note layer, that word is gone.

Opaque directories: hiding an entire folder

A related trick handles the case of deleting or replacing an entire directory rather than a single file. overlay2 marks a directory as opaque by setting a special extended attribute (trusted.overlay.opaque=y) on the upperdir copy of that folder. An opaque directory tells the kernel to stop looking further down into any lowerdirs for that folder’s contents — effectively resetting it, which is exactly the behavior Dockerfile instructions like removing and recreating a directory rely on.

A note on hard links across layers

Hard links are files that share the same underlying data on a native filesystem, and they behave a little differently once layers get involved. A hard link created entirely within a single layer works exactly as expected. But if a file that already exists in a lower layer gets a new hard link created against it from inside a running container, the kernel can’t create a true hard link that spans a read-only lower layer and a writable upper layer — instead, it performs a copy-up of the target file into upperdir first, and the new link is created against that upperdir copy. The result is correct from the application’s point of view, but it’s another situation, alongside plain file edits, where an operation that looks cheap on a native filesystem quietly costs a full copy-up on overlay2.

sequenceDiagram
    participant P as Process in container
    participant M as merged view
    participant U as upperdir
    participant L as lowerdir (image layer)
    P->>M: write("/etc/config.txt", newData)
    M->>U: does config.txt exist in upperdir?
    U-->>M: no
    M->>L: copy-up config.txt (full file)
    L-->>U: file copied into upperdir
    M->>U: apply write to upperdir copy
    U-->>P: write succeeds
    Note over L: original in lowerdir is never touched
        
FIG 2 — Copy-up sequence when a container modifies a file that originates in a lower layer

5Data Flow & Lifecycle

Tracing a container from docker pull to docker rm shows exactly when and where overlay2 gets involved at each stage.

1

Image pull

Docker downloads each compressed layer, verifies its digest, and extracts it into its own diff folder under /var/lib/docker/overlay2/. No mounting happens yet — these are just plain directories of files sitting on disk.

2

Container creation

Docker creates a brand-new writable layer folder for this specific container, containing empty diff and work subdirectories, and writes a lower file listing every image layer’s short symlink, in order.

3

Container start

Docker issues the actual OverlayFS mount syscall, combining all the lowerdirs (image layers, read-only) with the new upperdir (writable) and workdir, and exposing the result at merged. This merged path becomes the container’s root filesystem.

4

Runtime reads & writes

Reads are served from whichever layer — upper or lower — has the most recent copy of a file. Writes always land in upperdir, triggering copy-up for any pre-existing file being modified for the first time.

5

Container stop

The OverlayFS mount is torn down, but the upperdir folder on disk is left exactly as it was — nothing is deleted. A stopped container can be started again and will see the same writable state.

6

Container removal

Only now does Docker delete the container’s upperdir, workdir, and merged folders from disk. The underlying image layers are untouched and remain available for any other container built from the same image.

i
This is why containers are “ephemeral”

Everything a container writes lives only in its own upperdir. The moment step 6 happens, that upperdir — and everything unique the container ever wrote — is gone forever, unless the data was explicitly placed in a Docker volume or bind mount, which live outside the overlay2 layer stack entirely.

The optional seventh step: turning a container’s changes into a new image

There’s one more transition worth understanding, even though it happens less often in normal workflows: committing a running or stopped container into a brand-new image. When this happens, Docker takes everything currently sitting in that container’s upperdir — including any whiteout markers recording deletions — and packages it as a fresh, standalone image layer, placed on top of the exact lower layers the container was already using. That new layer gets its own content digest and can now be pushed, shared, or used as the base for further containers, exactly like any layer that originated from a Dockerfile instruction. It’s the same copy-up-and-whiteout machinery from earlier chapters, just observed from the image-authoring side rather than the runtime side.

6Layer Sharing & Image Composition

This chapter explains the payoff of the whole design: why ten containers built from the same base image barely use any extra disk space, and why docker build caching is so effective.

Content-addressable layers

Every image layer is identified by a cryptographic hash of its own content. This means if two completely different images both happen to start with, say, the exact same Ubuntu base layer, Docker recognizes the identical hash and stores that base layer’s files exactly once on disk — both images simply reference the same diff folder as one of their lowerdirs.

Simple Analogy

It’s like a shared corporate template library. A hundred different PowerPoint decks across a company can all “use” the same company-logo slide master without each deck secretly containing a hundred separate copies of that slide — they just all point back to the one shared master file.

1x
Base layer stored on disk, no matter how many images reuse it
O(1)
Container start time relative to image size, since only a mount is performed
0 bytes
Copied for a container until it actually writes something

Why docker build caching works

Each Dockerfile instruction that changes the filesystem (RUN, COPY, ADD) produces one new layer, hashed from its inputs. If you rebuild an image and an earlier instruction’s inputs haven’t changed, Docker reuses that layer’s existing hash from cache instead of re-executing it — which is exactly why Dockerfile authors are taught to put frequently-changing instructions (like copying application source code) near the bottom of the file: it maximizes how many layers above it stay cache-hit on every rebuild.

“A Docker image isn’t a single artifact — it’s an address book of reusable, content-hashed directories that overlay2 knows how to stack on demand.”

From manifest to mount: how a chain of hashes becomes a stack of directories

An image’s manifest — the JSON document that a registry actually serves when you pull an image — lists its layers purely as an ordered array of content digests; it says nothing about local folder names at all. When Docker pulls an image, it walks that list top to bottom, and for each digest checks its local metadata database for a matching, already-extracted layer before downloading anything. Only missing layers are actually fetched and decompressed. Once every layer in the chain is present locally, Docker resolves each digest to its local cache-id, and it’s only that final list of cache-ids that ever gets handed to the kernel as the overlay2 mount’s lowerdir string.

StageWhat identifies a layer here
Registry manifestDistributable content digest (what you’d verify against a signature)
Local image storeDiffID (hash of the uncompressed layer content)
overlay2 on-disk foldercache-id (host-local, arbitrary, stable only on this machine)
Kernel mount syscallShort symlink name inside l/ (to fit the mount argument length limit)

This layered chain of identities is what allows the same conceptual “layer” to be verified cryptographically when transferred over the network, deduplicated logically inside Docker’s own bookkeeping, and represented as a short, filesystem-friendly name at the point the kernel actually needs to build a mount — each identity is optimized for the concern at the stage it’s used.

7Where the Design Breaks Down: Limits & Edge Cases

overlay2 is elegant, but it inherits real constraints from both the Linux kernel and the filesystem underneath it. Knowing these limits in advance saves painful production debugging later.

Layer count

Max lowerdir stack depth

Older kernels capped the number of stackable lowerdirs at 128; exceeding it via extremely long, uncollapsed Dockerfiles or deep multi-stage builds can fail a container’s start.

Mount arg length

Page-size mount string

Even with the short-symlink workaround in l/, an extreme number of layers can still overflow the kernel’s mount option string limit.

Filesystem requirement

Needs d_type support

overlay2 requires the underlying filesystem (typically ext4 or xfs) to support directory entry type info (d_type). Some older or misconfigured xfs setups disable this and silently break overlay2.

Semantics

Not fully POSIX-identical

A few rare operations — like certain hard-link and rename edge cases across layer boundaries — don’t behave identically to a native filesystem, which occasionally surprises very filesystem-sensitive applications.

!
Common Mistake

Running databases with their data directory left inside the container’s writable layer (instead of a bind mount or named volume). Beyond the “data vanishes on docker rm” problem, heavy random-write database workloads on overlay2’s copy-up mechanism are noticeably slower than on a native filesystem — this is precisely why official database images document a VOLUME for their data path.

Filesystem feature requirements, explained

The d_type requirement mentioned above deserves a plain-English explanation, because it trips people up in the wild more often than the layer-count limit does. When the kernel scans a directory, each entry can optionally carry a quick hint about what kind of object it is — a regular file, a directory, a symlink — without needing a separate, more expensive lookup call to find out. OverlayFS relies on this hint being present and accurate to correctly merge entries across layers and to know when a whiteout marker is standing in for a deleted directory versus a deleted file. Some older xfs filesystems were formatted with this feature turned off by default, and Docker will refuse to use overlay2 on top of such a filesystem, reporting an explicit error rather than risking silently incorrect behavior.

Nested containers: overlay-on-overlay

A less commonly discussed limitation shows up in Docker-in-Docker setups, where a container itself runs a Docker daemon that also wants to use overlay2 for its own, inner containers. Historically, the Linux kernel restricted or outright refused using an OverlayFS mount as the lowerdir input to another OverlayFS mount, since the underlying filesystem already lacks certain low-level features (like fully generic extended-attribute passthrough) that a second overlay layer depends on. In practice this means nested Docker daemons commonly need to be configured with a different storage driver, or run against a dedicated volume formatted with a fully-featured filesystem, rather than simply inheriting overlay2 by default.

8Advantages, Disadvantages & Trade-offs

overlay2 became Docker’s default for good reasons, but “default” doesn’t mean “best for every workload.” Here’s the honest balance sheet, followed by how it stacks up against the alternatives.

Advantages

  • Native kernel implementation — no extra userspace daemon or kernel module to install, unlike devicemapper or zfs on Linux.
  • Very fast container startup, since starting a container is “just” a mount syscall, not a data copy.
  • Efficient shared storage — identical layers across many images and containers are stored once.
  • Simple, human-readable on-disk layout that’s straightforward to inspect and back up.
  • Page cache friendly — because files are read through a normal mount, the kernel’s page cache works transparently, unlike some block-based drivers.

Disadvantages / Trade-offs

  • Whole-file copy-up penalty for large files modified in place.
  • Layer-count and mount-argument ceilings on very deep image histories.
  • Slightly non-standard semantics for some rename/hard-link edge cases.
  • Heavy random-write workloads (databases, some CI caches) generally perform better on a dedicated volume than directly on overlay2’s writable layer.

How it compares to sibling storage drivers

DriverLayer strategyRequires special setup?Typical status today
overlay2Kernel union filesystem (OverlayFS)No — works on ext4/xfs out of the boxDefault on virtually all modern Linux distros
aufsOlder union filesystem, predecessor conceptRequires a separate kernel patch/moduleLargely retired in favor of overlay2
devicemapperBlock-level snapshots (thin provisioning)Needs dedicated block device / loopback setupLegacy; mostly seen on very old RHEL/CentOS systems
btrfsNative filesystem-level subvolume snapshotsRequires a btrfs-formatted /var/lib/dockerNiche, used when the host already runs btrfs
zfsNative filesystem-level snapshots & clonesRequires ZFS kernel module + zpoolPopular in storage-focused or FreeBSD-adjacent shops
fuse-overlayfsUserspace re-implementation of OverlayFSNo root privileges requiredUsed for rootless Docker/Podman

A mental model for reasoning about performance trade-offs

Rather than memorizing specific benchmark numbers — which shift with kernel version, disk hardware, and workload — it’s more durable to reason from the mechanism itself. overlay2 excels whenever a workload’s dominant pattern is “read a lot, occasionally write small, mostly-new files,” because reads hit the shared page cache and writes stay contained to a single small upperdir. It struggles relatively more whenever a workload repeatedly rewrites large, pre-existing files in place, because each first-touch triggers a full copy-up. Framed this way, the earlier database and large-log-file warnings aren’t arbitrary rules of thumb — they’re direct consequences of this one mechanism.

Network filesystem incompatibility

One more practical constraint worth flagging here: OverlayFS has long had limited, inconsistent, or outright unsupported behavior when either its lowerdir or upperdir sits on a network filesystem such as NFS. This is a direct consequence of certain filesystem features (extended attributes, consistent d_type reporting, specific locking semantics) that OverlayFS depends on but that network filesystems don’t always implement identically to a local one. Production Docker hosts are expected to keep /var/lib/docker on genuine local storage for this reason.

9Security Considerations

Storage drivers aren’t usually top-of-mind for container security, but overlay2’s behavior has direct, practical consequences.

Secrets baked into a layer never truly disappear

If a Dockerfile RUN instruction writes a secret file and a later instruction deletes it, overlay2 has already committed that secret into an earlier layer’s diff folder. The whiteout in the later layer only hides it from the merged view — the raw bytes remain fully extractable from the image tarball. This is exactly why multi-stage builds and dedicated secret-mounting mechanisms exist.

Capability requirements for privileged operations

Performing an OverlayFS mount traditionally requires elevated privileges on the host, which is one reason classic Docker runs a root-owned daemon. Rootless Docker sidesteps this with fuse-overlayfs, trading a little performance for a meaningfully smaller privilege footprint.

Shared lower layers are a read-only trust boundary

Because many containers can mount the very same lowerdir simultaneously, the read-only guarantee of lowerdirs is a genuine security property, not just an implementation detail — it’s what prevents one compromised container from tampering with a base image layer that other, unrelated containers are also using.

i
Practical takeaway

Treat every byte ever written during an image build as permanently public within that image, regardless of later deletions — scan images for secrets, and prefer multi-stage builds or BuildKit’s dedicated secret-mount feature over “write then delete” patterns.

Defense in depth beyond the storage driver

It’s worth being clear-eyed that overlay2, like any storage driver, is implemented in kernel code and has, at various points in its history, been the subject of security patches addressing specific bugs — this is true of essentially every actively developed kernel subsystem, not a special weakness unique to overlay2. The practical takeaway isn’t to distrust overlay2 specifically, but to apply the same baseline hygiene that applies to any kernel-adjacent container component: keep host kernels patched and up to date, run containers with the least privilege they actually need (avoiding unnecessary root or extra capabilities), and treat the storage driver as one layer in a broader defense-in-depth strategy rather than a security boundary on its own.

10Monitoring, Logging & Metrics

overlay2 problems tend to show up as either “disk is unexpectedly full” or “container start is unexpectedly slow.” Both have a small, predictable set of things worth watching.

Disk usage

Per-object breakdown

Docker’s built-in disk-usage reporting separates space used by images, containers (their writable layers), and volumes — the first place to look when overlay2 storage seems to be growing unexpectedly.

Inode pressure

Inode exhaustion

Union filesystems with many small files across many layers can exhaust filesystem inodes well before raw disk space runs out — a classic overlay2 surprise on ext4 filesystems formatted with too few inodes.

Orphaned data

Dangling layer directories

Interrupted pulls or crashed builds can leave orphaned diff folders under /var/lib/docker/overlay2/ that no image references — periodic pruning reclaims this space.

Kernel-level visibility

dmesg & kernel logs

OverlayFS mount failures, d_type incompatibility, and layer-limit errors surface in the kernel ring buffer, not in Docker’s own logs — worth checking first for any “container won’t start” storage error.

SymptomLikely overlay2-related cause
“No space left on device” despite free disk spaceInode exhaustion on the underlying filesystem
Container fails to start with a mount errorLayer stack too deep, or corrupted/missing lower symlink in l/
Disk usage keeps climbing after cleanupOrphaned layer directories not yet garbage-collected
Slow writes inside a specific container onlyLarge in-layer file repeatedly triggering full copy-up

Where these metrics surface in day-to-day tooling

Docker’s own disk-usage summary breaks total storage down into images, containers, local volumes, and a build-cache category, along with how much of each is “reclaimable” versus actively in use — this single view is usually the fastest way to see whether unexpected growth is coming from dangling image layers, stopped-but-not-removed containers, or genuinely active data. At a cluster level, tools like cAdvisor and the metrics it feeds into Prometheus-based monitoring stacks report per-container filesystem usage by inspecting each container’s overlay2 upperdir directly, which is how dashboards can show “container X is using Y MB of writable storage” without needing any cooperation from the application running inside that container.

i
A useful habit

Check disk-usage and prune reports on a schedule rather than only when a host actually runs out of space — overlay2’s efficient sharing means growth is often gradual and easy to miss until an unrelated deploy suddenly tips a host over its limit.

11Design Patterns & Anti-patterns

Most of what separates a lean, fast-building image from a bloated, slow one comes down to how well the author worked with overlay2’s layer model instead of against it.

ANTI-PATTERN-01 Avoid
Problem

Splitting a single logical change across many separate RUN instructions (e.g. one RUN apt-get update, another RUN apt-get install, another RUN rm -rf /var/lib/apt/lists/*).

Why It’s Harmful

Each instruction becomes its own layer. The cleanup in the final RUN only whiteouts the files in the merged view — the package cache from the earlier layer is still physically present on disk and still gets shipped inside the image.

Correct Approach

Chain related filesystem-changing commands into a single RUN instruction with && so the download, install, and cleanup all happen inside one layer’s diff, keeping the temporary files out of the final image entirely.

ANTI-PATTERN-02 Avoid
Problem

Copying the entire application source tree, including dependency manifests, in one COPY . . instruction near the top of the Dockerfile.

Why It’s Harmful

Any source file change — even a one-line comment — invalidates that layer’s hash, and with it every layer built on top, including the expensive dependency-install step, forcing a full re-download/re-install on every rebuild.

Correct Approach

Copy only the dependency manifest first, run the install, and copy the rest of the source afterward — so dependency layers stay cache-hit across ordinary code changes.

Good pattern: multi-stage builds

Multi-stage builds let an image use one temporary “build” stage full of compilers and build tools, then copy only the final compiled artifact into a clean final stage. Because each stage produces its own independent layer stack, the bulky build-tool layers are never part of the final image’s lowerdir chain at all — they simply never get referenced.

ANTI-PATTERN-03 Avoid
Problem

Building an image from a directory that contains large, irrelevant files — local dependency caches, log output, version control history, editor artifacts — without excluding them from the build context.

Why It’s Harmful

Anything copied into an image via COPY or ADD becomes permanent content inside a layer’s diff folder, bloating every downstream pull, every layer-sharing calculation, and every container’s effective lowerdir stack, even though none of it is needed at runtime.

Correct Approach

Maintain an explicit ignore file for the build context so irrelevant paths never reach the build in the first place, keeping each layer’s diff limited to only what the running application actually needs.

Good pattern: squashing intentionally, not by default

Some teams reach for image “squashing” — collapsing an entire layer stack into one — as a blanket size-reduction technique. It’s worth applying deliberately rather than reflexively: squashing throws away the very layer-sharing benefit this whole tutorial has been explaining, since a squashed image can no longer share individual layers with any other image at all. It’s a reasonable trade for a final, rarely-rebuilt distribution artifact, but a poor default for images that are rebuilt frequently or that share a common base with many siblings.

12Best Practices & Common Mistakes

A short, practical checklist distilled from everything covered so far.

Do

Use volumes for write-heavy data

Databases, caches, and log directories should live in a named volume or bind mount, bypassing overlay2’s copy-up path entirely.

Do

Order Dockerfile instructions by change frequency

Rarely-changing instructions first, frequently-changing ones last, to maximize layer cache hits on rebuilds.

Do

Prune regularly

Periodically remove unused images, stopped containers, and dangling layers so orphaned overlay2 directories don’t silently accumulate.

Avoid

Massive single layers

One giant RUN instruction that installs everything at once is efficient for size, but makes rebuild caching and debugging individual steps harder — balance layer count against layer granularity deliberately.

Avoid

Writing large files inside the container layer

Modifying large in-layer files repeatedly triggers expensive whole-file copy-ups; redirect that I/O to a volume instead.

Avoid

Assuming “deleted” means “gone”

Whiteouts hide files from the merged view but never remove earlier bytes from the image — plan secret handling accordingly.

Do

Favor multi-stage builds for compiled languages

Keep build toolchains entirely out of the final image’s layer chain rather than installing and later “removing” them in the same stage.

Avoid

Manually editing /var/lib/docker/overlay2/

Always go through Docker’s own commands for removing images, containers, and layers so the on-disk state and Docker’s internal metadata database stay in sync.

13Real-World & Industry Examples

overlay2 isn’t confined to Docker Desktop toy examples — it’s the storage foundation under an enormous share of production container infrastructure.

Default across modern Linux distributions

Ubuntu, Debian, Fedora, and most current RHEL/CentOS derivatives configure overlay2 (or the equivalent overlayfs snapshotter) as the out-of-the-box storage backend for both Docker and containerd.

Kubernetes node runtimes

containerd, the container runtime underneath most managed Kubernetes offerings, uses an OverlayFS-based snapshotter as its default image storage mechanism — the same union-filesystem principles from this tutorial, applied at cluster scale across thousands of nodes.

CI/CD pipelines

Continuous integration platforms that spin up short-lived build containers rely heavily on overlay2’s fast, copy-free container start time — a pipeline that launches hundreds of ephemeral containers per hour would be far slower on a storage driver that had to physically duplicate filesystem data for each one.

Rootless container platforms

Podman and rootless Docker installations commonly rely on fuse-overlayfs, the userspace reimplementation of the very same lowerdir/upperdir model, to deliver union-filesystem behavior to unprivileged users who can’t perform a real kernel mount.

Managed cloud container services

Major managed container and serverless-container offerings that run standard Linux container images underneath the hood inherit the same overlay2/OverlayFS-based layer sharing at the node level, which is part of how these platforms keep per-instance cold-start times low even when many customer workloads share the same underlying compute fleet.

Local developer workflows

Even outside production, the everyday developer experience of running docker build dozens of times a day and having most layers “just be there instantly” on rebuild is a direct, visible product of overlay2’s content-addressable, shared-layer design — it’s arguably the single design decision developers interact with most, even if they never see the term “overlay2” itself.

14Redirects, Metadata Copy-Up & the Index Feature

Copy-up and whiteouts explain most of what overlay2 does day to day, but two subtler kernel features quietly fix problems the basic model would otherwise have with renamed directories and repeated copy-ups.

The renamed-directory problem

Imagine a directory named /app/old-config exists down in a lower layer, and a container renames it to /app/new-config. A naive union filesystem would be stuck: it can’t move the directory in the read-only lowerdir, and simply creating a new upperdir directory called new-config wouldn’t automatically know to “inherit” the old directory’s hidden lower-layer contents once merged. OverlayFS solves this with a feature called redirect_dir.

Simple Analogy

It’s like a library changing a book’s shelf location. Instead of physically moving every page, the librarian just updates the card catalog with a note: “this title now lives at shelf B-12, but see shelf A-4 for the original copy.” A reader following the catalog entry is seamlessly redirected — the actual pages never needed to move.

When a rename like this happens, overlay2 creates the new directory in upperdir and stores a special extended attribute on it — trusted.overlay.redirect — pointing back to the original path down in the lowerdir stack. When the kernel builds the merged view and encounters that attribute, it knows to keep resolving that directory’s original lower-layer contents under the new name, instead of treating it as an empty new folder.

Metadata-only copy-up

Recall from the earlier chapter that copy-up duplicates an entire file’s content into upperdir the moment any part of it is modified. Newer kernels soften this for a common special case: changes that only touch a file’s metadata — its permissions, ownership, or timestamps — rather than its actual byte content. In that situation, the kernel can perform a lightweight “metadata-only” copy-up, recording just the changed attributes without duplicating the full file data, and only falling back to a full data copy-up once the file’s content itself is actually written to.

i
Why this matters in practice

A very common container action — running chown or chmod across a directory tree at startup to fix file ownership for a non-root user — used to force a full copy-up of every single file it touched, even ones whose content never changed. Metadata-only copy-up makes this class of startup operation dramatically cheaper on large trees.

The index feature and hard-link tracking

OverlayFS also maintains an internal index directory that tracks the relationship between an upperdir copy of a file and its original lower-layer counterpart, primarily to correctly support hard links and to detect certain classes of inconsistency across a layer stack safely. This index is entirely internal bookkeeping — you’ll see its footprint on disk, but Docker and application code never interact with it directly.

Feature

redirect_dir

Lets a renamed or moved directory still resolve its original contents from lower layers correctly.

Feature

Metadata-only copy-up

Avoids a full file-content copy when only ownership, permission, or timestamp metadata changes.

Feature

Index

Internal tracking structure supporting correct hard-link behavior and layer-stack consistency checks.

Availability

Kernel-version dependent

These refinements arrived incrementally across kernel releases after the original overlay2 merge, so their exact behavior can vary slightly between older and newer hosts.

15Page Cache, Memory & I/O Performance

Because overlay2 is implemented as a genuine kernel filesystem rather than a block-device abstraction, it interacts with Linux memory management in ways that are worth understanding on their own.

Shared page cache across containers

When two different containers both read the same unmodified file from a shared lower layer — say, the same shared C library from an identical base-image layer — the Linux kernel’s page cache can serve both reads from the very same physical memory pages. The file is only read from disk once; every subsequent container reading that same lower-layer file benefits from the page already being cached in RAM.

Simple Analogy

Picture a popular reference book kept on a librarian’s front desk instead of the shelves, because so many people ask for it. The first request fetches it from the stacks; every request after that is served instantly from the desk, no matter how many different patrons ask — as long as nobody has “written in” their own personal copy.

Where sharing stops: after copy-up

The moment copy-up duplicates a file into a container’s upperdir, that container now has its own private copy — and its own private page-cache entry for it going forward. Ten containers that have each independently modified the same originally-shared configuration file no longer share any caching benefit for that file; each container’s writes and subsequent reads are now isolated to its own upperdir copy.

Memory accounting and cgroups

Page-cache pages backing a container’s files are generally accounted against that container’s memory cgroup when the container is what caused them to be read or written, which is part of why an image with very large files, or a container that reads a lot of data, can appear to consume more of its memory limit than its actual process heap would suggest — the kernel is counting cached file pages, not just active application memory.

!
Common Mistake

Assuming a container’s memory usage graph only reflects “real” application memory. A container that streams through a large log file or dataset can show a memory usage spike from page cache alone, even though the application’s actual working set is small — this is normal kernel behavior, not a leak, but it regularly confuses teams new to container memory metrics.

Why overlay2 generally outperforms block-based drivers for read-heavy workloads

Because reads pass through the normal Linux filesystem and page-cache path, applications doing mostly reads against image content — a typical web server serving static assets baked into the image, for instance — tend to perform close to native filesystem speed. Block-based drivers like devicemapper, by contrast, introduce an extra layer of block-device indirection for every I/O operation, which is one of the reasons the ecosystem broadly moved toward overlay2 as those older drivers matured out of favor.

“overlay2’s whole personality — fast starts, shared storage, cheap reads, and occasionally expensive first-writes — falls out of one design choice: real files on a real filesystem, merged live by the kernel, instead of duplicated in advance.”

16Frequently Asked Questions

Q1Is overlay2 a separate filesystem I need to format a disk as?

No. overlay2 is a Docker storage driver that sits on top of an already-formatted filesystem (typically ext4 or xfs) and uses the kernel’s OverlayFS feature to stack directories from that underlying filesystem.

Q2Why do my images take up much less combined space than the sum of each image’s reported size?

Because shared base layers between images are stored exactly once on disk. The “size” reported per image includes shared layers in its total, so the numbers overlap rather than add up.

Q3Does deleting a file inside a container actually free up disk space in the image layers?

No. A whiteout only hides the file from the merged view of a running container; the original bytes remain in whichever lower (image) layer they were created in.

Q4Why is my container writing to a database noticeably slower than the same database running directly on the host?

Most likely the database’s data directory is sitting on overlay2’s writable layer, so every modified page inside its data files triggers a whole-file copy-up the first time it’s touched. Mounting the data directory as a volume removes this overhead entirely.

Q5Can I mix overlay2 with other storage drivers on the same host?

Docker uses exactly one storage driver per daemon at a time; changing it typically requires re-pulling images and rebuilding containers, since the on-disk layout is driver-specific.

Q6Is overlay2 still Docker’s recommended default today?

Yes — for any reasonably modern Linux kernel and filesystem combination, overlay2 remains the officially recommended and most widely used storage driver.

Q7Can I run overlay2’s lowerdir or upperdir on an NFS-mounted directory?

Generally no — OverlayFS has historically had limited or unreliable support for network filesystems like NFS as a lower or upper layer, so /var/lib/docker is expected to sit on a genuine local filesystem such as ext4 or xfs.

Q8Does running a container inside another container (Docker-in-Docker) cause problems for overlay2?

It can. Stacking overlay2 on top of overlay2 (an overlay filesystem as the lowerdir for another overlay mount) has historically been unsupported or restricted by the kernel, which is why Docker-in-Docker setups often fall back to a different storage strategy inside the nested daemon.

Q9Why does docker inspect show a different hash than the folder name under overlay2/?

The folder name is a local, host-specific cache-id, while docker inspect and docker images typically display content-addressable digests (the layer’s DiffID or the distributable digest). Docker keeps an internal mapping between the two; they’re deliberately different identifiers for different purposes.

Q10Is it safe to manually delete folders under /var/lib/docker/overlay2/ to free space?

Not directly — Docker’s internal metadata database tracks which layers belong to which images and containers, and manually deleting a folder it still references can corrupt that bookkeeping. Use Docker’s own pruning commands, which safely remove only genuinely unreferenced layers.

17Summary and Key Takeaways

overlay2 turns the abstract idea of a “container image” into something concrete and efficient: read-only stacked layers on disk, merged live by the kernel, with a thin writable layer capturing whatever a specific container changes. Nothing is copied until it has to be, nothing is truly deleted until the layer that created it is gone, and the entire system is built on two elegantly simple tricks — copy-up and whiteouts — repeated consistently at every level, from a single file edit to an entire multi-gigabyte image.

Key Takeaways

  • overlay2 is a Docker storage driver — it wraps the Linux kernel’s OverlayFS feature; it isn’t a filesystem you format a disk with.
  • Four directory roles matter — lowerdir (read-only image layers), upperdir (container writes), merged (unified view), workdir (kernel scratch space).
  • Copy-up powers “editing” read-only files — the whole file is copied into upperdir before any change is applied, which is expensive for large files.
  • Whiteout markers fake deletion — the original data in a lower layer is never actually removed, only hidden from the merged view.
  • Shared layers are the efficiency secret — identical content-hashed layers are stored once on disk no matter how many images or containers reference them.
  • Every container’s writable data disappears on removal — unless it was placed in a volume or bind mount outside the overlay2 stack.
  • Dockerfile authoring choices directly shape overlay2 behavior — instruction ordering and grouping control both build-cache efficiency and final image size.