A volume is just a directory on the host filesystem, bind-mounted into the container’s namespace.

What actually happens

When Docker creates a volume, it makes a directory on the host:

/var/lib/docker/volumes/<volume-name>/_data/

That’s it. Just a normal directory. No special filesystem, no format, no encryption — you could ls it from the host right now.

When a container mounts it, the kernel performs a bind mount — a Linux feature that makes a directory appear at a second location in the filesystem tree simultaneously:

host:  /var/lib/docker/volumes/mydata/_data/
           ↕  (same inodes, same blocks)
container: /app/data/

Both paths point to the exact same inodes and disk blocks. There’s no copying, no syncing — it’s one directory visible from two places at once. Writes from the container are immediately visible on the host and vice versa.

How this differs from the image/container layer

The OverlayFS writable layer (the container’s copy-on-write layer) lives inside Docker’s overlay stack and is destroyed when the container is removed. A bind-mounted volume sits outside that stack entirely — it bypasses OverlayFS and goes straight to the host filesystem. That’s why data survives container removal.

The three storage types compared at OS level

TypeOS mechanismLives atSurvives container removal
VolumeBind mount → host dir/var/lib/docker/volumes/Yes
Bind mountBind mount → host dirWherever you specifyYes
tmpfstmpfs kernel mountRAM onlyNo (gone on stop)

Bind mount vs volume — what’s the difference then?

Functionally identical at the kernel level — both are bind mounts. The difference is just who manages the directory:

  • Volume — Docker creates and manages the directory; you reference it by name
  • Bind mount — you specify an exact host path yourself (-v /home/user/code:/app)

One sentence: a volume is a host directory bind-mounted into the container’s filesystem namespace — the kernel exposes the same inodes at two paths simultaneously, with no virtualisation or copying involved.