CoursesDocker in depthStorage & volumes

Storage drivers & the layered filesystem

overlay2 and copy-on-write.

Intermediate12 min · lesson 10 of 30

The storage driver is the engine component that implements image layers and copy-on-write on disk. The modern default is overlay2, a union filesystem: it stacks the image’s read-only layers as lower directories, the container’s writable layer as an upper directory, and presents a single merged view to the container. Older drivers (devicemapper, aufs, btrfs) exist for historical or special cases, but overlay2 is what you will run on any current kernel.

OVERLAY2: MERGING READ-ONLY AND WRITABLE LAYERS
lowerdir — image layers (read-only)
base layer
dependency layer
app layer
upperdir — container layer (writable)
new + modified files
the container's changes
copy-up on first write
whole file copied from a lower layer
merged — what the container sees
single unified view
reads come from the topmost layer with the file
Large-file writes pay a full copy-up, so put database data on a volume to bypass the union/CoW machinery.
terminal
$ docker info -f '{{ .Driver }}'
overlay2
$ docker info -f '{{ json .DriverStatus }}'
[["Backing Filesystem","extfs"],["Supports d_type","true"],["Using metacopy","false"]]

How overlay2 works

Each layer is a directory under /var/lib/docker/overlay2. The kernel’s overlay filesystem merges the read-only lowerdir (image layers) with the writable upperdir (the container’s layer): reads come from the topmost layer that has the file, and the first write to a lower-layer file copies the whole file up to the upperdir before modifying it. That copy-up is cheap for small files and increasingly expensive as files grow.

terminal
$ sudo ls /var/lib/docker/overlay2 | head -3 # one dir per layer
1a2b3c...
4d5e6f...
$ sudo mount | grep overlay | head -1
overlay on /var/lib/docker/overlay2/<id>/merged type overlay
(lowerdir=...:...,upperdir=.../diff,workdir=.../work)

Scenario: writes to a large file are slow

A workload that rewrites a large file — a database file, a big log, an on-disk cache — is sluggish, and the reason is copy-on-write: the first modification copies the entire file from a lower layer into the writable layer. The fix is to put that data on a volume, which writes straight to the backing filesystem and bypasses the union/CoW machinery entirely. This is the concrete reason “databases go on volumes.”

terminal
# DB data on a volume -> direct writes, no overlay copy-up, and it persists
$ docker run -d --name db \
--mount type=volume,source=pgdata,target=/var/lib/postgresql/data \
postgres:16

Scenario: check the driver and move Docker’s data disk

On a new host you want to confirm the driver is healthy and, often, move Docker’s storage onto a dedicated disk so images and volumes do not fill the root partition. Confirm overlay2 with a backing filesystem that supports d_type (ext4 or xfs with ftype=1 — without it overlay2 misbehaves), then repoint data-root in daemon.json to the mounted disk and restart.

/etc/docker/daemon.json
{
"storage-driver": "overlay2",
"data-root": "/mnt/docker-data" // a dedicated disk, not the root partition
}
// verify after restart: docker info | grep -E "Storage Driver|Docker Root Dir"
Do not run databases on the writable layer
A database whose data directory sits on the container’s overlay writable layer pays copy-on-write on every change and loses everything when the container is removed. Always mount a volume for database data — it is faster (no CoW) and it is the only thing that makes the data durable across the container’s lifecycle.