06 · Storage Drivers & Volume Plugins¶
Every image layer and every container's writable layer has to be represented on disk somehow. The storage driver decides how; a volume plugin extends where volume data can physically live, beyond the local host's filesystem.
Checking your storage driver¶
overlay2 is the modern default on Linux and the one covered throughout
this course's "How It Actually Works" sections; older/alternative
drivers (aufs, devicemapper, btrfs, zfs) exist for specific
kernel/filesystem combinations but overlay2 is the right default choice
for nearly all current Linux hosts.
How overlay2 layers a filesystem, at the directory level¶
docker inspect -f '{{.GraphDriver.Data.MergedDir}}' mycontainer
docker inspect -f '{{.GraphDriver.Data.UpperDir}}' mycontainer
docker inspect -f '{{.GraphDriver.Data.LowerDir}}' mycontainer
LowerDir: one or more read-only directories, one per image layer, stacked in order.UpperDir: the single writable directory unique to this container — everything the container writes lands here.MergedDir: the unified view the container's process actually sees as its root filesystem — overlayfs presentsLowerDir+UpperDirmerged into one coherent tree.
Volume plugins for non-local storage¶
The built-in local volume driver stores data on the host's own disk.
A volume plugin implements Docker's volume driver API to back
volumes with something else entirely — network storage, cloud block
storage, distributed filesystems:
docker plugin install rexray/ebs
docker volume create --driver rexray/ebs --name my-ebs-volume
docker run -d --name app -v my-ebs-volume:/data myapp
From inside the container, /data looks like an ordinary local
directory — the plugin handles attaching/mounting the underlying cloud
volume transparently. This matters for scenarios plain host storage
can't provide: a volume that must survive the host being terminated
(common in autoscaled cloud fleets), or one shared concurrently across
multiple hosts.
NFS as a built-in volume option (no plugin needed)¶
docker volume create --driver local \
--opt type=nfs \
--opt o=addr=192.168.1.100,rw \
--opt device=:/exported/path \
nfs-volume
docker run -d -v nfs-volume:/data myapp
The local driver itself supports mounting NFS shares as "volumes"
without a third-party plugin, by shelling out to the same mount
options you'd otherwise use directly — a lighter-weight option than a
full plugin when NFS is the only extra capability you need.
Worked example: inspecting layer sharing directly¶
docker run -d --name c1 python:3.12-slim sleep 3600
docker run -d --name c2 python:3.12-slim sleep 3600
docker inspect -f '{{.GraphDriver.Data.LowerDir}}' c1
docker inspect -f '{{.GraphDriver.Data.LowerDir}}' c2
# identical LowerDir chains — both containers share the exact same
# read-only image layers on disk; only their (separate) UpperDir differs
How It Actually Works¶
What overlayfs actually does at the kernel level. overlay2 is a
real Linux kernel filesystem (mount -t overlay), not a Docker
invention — Docker calls mount with lowerdir=, upperdir=, and
workdir= options for each container. The kernel then presents a
merged view where a file lookup checks upperdir first and falls back
through the lowerdir stack; a write to a file that only exists in a
lower (read-only) layer triggers copy-up: the kernel transparently
copies that file into upperdir first, then applies the write there —
the original lower-layer copy is untouched. This copy-up mechanism is
the literal, kernel-level explanation for why editing a large file that
originated in a base image (say, replacing one line in a multi-gigabyte
file shipped in python:3.12) is unexpectedly slow and grows the
container's writable layer by the entire file's size, not just the
size of your edit — the kernel had to copy the whole file up before it
could apply any part of the write.
Why a whiteout file, not real deletion, represents "delete" across
layers. Because lower layers are strictly read-only, overlay2 can't
actually erase a file that lives in one. Deleting such a file instead
creates a special zero-size character device (major/minor number 0/0)
in upperdir with the same name — a "whiteout" — which the kernel's
overlay lookup logic recognizes and uses to hide the lower layer's
version from the merged view, without touching the lower layer's actual
bytes at all. This is the same mechanism referenced back in Level 1's
Dockerfile lesson (why RUN rm in a later layer doesn't shrink the
image) — the whiteout is created in that later layer, while the deleted
file's bytes remain physically present in whichever earlier layer
originally introduced them, still counted in the image's total size on
disk and during registry push/pull.
Exercise¶
Start a container from any base image, docker exec into it and modify
a file that came from the base image (not one you COPY'd in), then
inspect docker diff <container> to see it reported as changed (C).
Cross-reference .GraphDriver.Data.UpperDir on the host (as root, if
needed) and confirm the modified file's full contents now exist there
via copy-up, even though you changed only a few bytes.