Docker Storage on a Multi-Volume Host¶
If / is small and a second volume holds Docker's data, redirecting Docker's storage there
is not enough. Docker Engine and containerd are two independent daemons with two independent
storage settings, and the one actually holding your image layers is easy to miss — right up
until the small volume fills.
Two daemons, two storage locations¶
- Docker Engine (
dockerd) —data-rootin/etc/docker/daemon.json, default/var/lib/docker. - containerd —
rootin/etc/containerd/config.toml, default/var/lib/containerd. This setting is entirely independent ofdata-root. Redirecting one does nothing to the other.
On a Docker install with the containerd image store enabled — check for driver-type:
io.containerd.snapshotter.v1 in docker info — Docker delegates image and container layer
storage to containerd's own content store and snapshotter, rather than managing it itself.
That's the layer that actually grows large, and it lives under containerd's root, not
Docker's data-root.
The trap: a host provisioned with a big secondary volume mounted at /var/lib/docker looks
fully relocated. It isn't. If containerd's root was never explicitly set, it silently
defaults to /var/lib/containerd — on whatever filesystem holds ordinary /var/lib, commonly
your small root volume — and grows there instead, unnoticed until root disk pressure shows up.
How to check what's actually using the space¶
sudo apt install ncdu # if not already present
sudo ncdu /var/lib
If containerd shows up large next to (or instead of) docker, work through this:
1. Is containerd's root actually configured?
sudo cat /etc/containerd/config.toml
On Debian/Ubuntu Docker installs, this file commonly ships with almost nothing in it —
disabled_plugins = ["cri"] and no explicit root line — meaning containerd is quietly using
its compiled-in default rather than anything intentionally set.
2. Is this containerd instance actually backing your Docker containers, not some unrelated
workload (Kubernetes, nerdctl, a stray install)?
systemctl status containerd --no-pager | grep -i namespace
Docker always runs its containers under the moby namespace. -namespace moby on every shim
process confirms it's Docker; anything else (k8s.io, for example) means something other than
Docker is also using this containerd instance, and needs its own accounting before you touch
anything.
3. Confirm from Docker's own side:
sudo docker info | grep -i -A2 'containerd\|driver-type'
Moving containerd's storage¶
This requires stopping Docker and every running container for the duration — plan a maintenance window, not a mid-day fix.
Give containerd its own volume, rather than nesting it inside /var/lib/docker. Nesting it
there works, but it quietly couples containerd's data to Docker's data-root's own blast
radius — a future "wipe and reinitialize Docker's data" runbook that does rm -rf
/var/lib/docker would take containerd's content store down with it as collateral damage, the
same risk this trap already caused once. If your volume group has room (vgs), a dedicated
logical volume avoids that:
# 1. Create and format a dedicated volume — adjust size and VG name to your host
sudo lvcreate -L 50G -n cairn-containerd vg0
sudo mkfs.ext4 /dev/vg0/cairn-containerd
# 2. Mount it where containerd will look for its data
sudo mkdir -p /var/lib/containerd-new
UUID=$(sudo blkid -s UUID -o value /dev/vg0/cairn-containerd)
echo "UUID=$UUID /var/lib/containerd-new ext4 defaults 0 2" | sudo tee -a /etc/fstab
sudo mount /var/lib/containerd-new
# 3. Point containerd at it
echo 'root = "/var/lib/containerd-new"' | sudo tee -a /etc/containerd/config.toml
# 4. Stop both daemons — Docker depends on containerd, stop it first
sudo systemctl stop docker
sudo systemctl stop containerd
# 5. Copy the existing data across
sudo rsync -aHAX /var/lib/containerd/ /var/lib/containerd-new/
# 6. Restart, containerd first
sudo systemctl start containerd
sudo systemctl start docker
# 7. Verify before deleting anything
sudo docker ps
sudo ncdu /var/lib
Once containers are confirmed healthy and /var/lib/containerd (the old path) has stopped
growing, keep it around briefly as a rollback, then remove it:
sudo mv /var/lib/containerd /var/lib/containerd.bak
# after a few days of confidence:
sudo rm -rf /var/lib/containerd.bak
If there's no free space left to allocate a new volume, nesting containerd's root under your
existing /var/lib/docker volume (e.g. /var/lib/docker/containerd) is the fallback — same
rsync procedure, just pointed at a subdirectory of the volume you already have, with the
blast-radius caveat above accepted rather than avoided.
Before you provision, not after¶
Setting this up correctly from the start avoids the stop/rsync/verify dance entirely:
- Set both
data-root(/etc/docker/daemon.json) and containerd'sroot(/etc/containerd/config.toml) to their final locations before the firstdocker pullordocker runon a new host — not justdata-root. - Re-check after any Docker upgrade. Whether the containerd image store is enabled, and by what
default, has changed across Docker Engine versions — a host that previously stored everything
under
data-rootcan start filling/var/lib/containerdagain after an otherwise routineapt upgrade, with nothing in your own configuration having changed. - A periodic
ncdu /var/lib(ordu -sh /var/lib/*) is the cheap early warning that catches this drift before it becomes a full root disk.
Next¶
- Self-Hosted Registry — the same "which directory actually holds
the bytes" question applies to
cairn-registry's owndata_dir; see itssetup/doctoroutput for the equivalent check on that role.