Skip to main content
RunBook Academy

Docker & ContainersXXX Β· Host MaintenanceStorage

Filesystem maintenance for /var/lib/docker

Advanced⏱ ~26 mindocker

What you'll learn

  • Lay out a Docker host so a full data root cannot take down the machine
  • Detect inode exhaustion, which fills a filesystem with free space remaining
  • Schedule discard for SSD-backed Docker storage
  • Move `data-root` to a larger filesystem without losing images or volumes

Prerequisites

Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11

Not yet marked complete on this device.

/var/lib/docker is not an ordinary directory. On a busy host it holds hundreds of thousands of small files across thousands of overlay layer directories, it is written to constantly, and β€” on most installations β€” it is on the root filesystem, where filling it takes down sshd, the journal and your ability to fix it.

Four maintenance concerns follow from that: where it lives, inodes, discard, and how to move it when it outgrows its home.

Where it should live

Read-only / Safecurrent layout
$ findmnt -T /var/lib/docker -o TARGET,SOURCE,FSTYPE,SIZE,USED,AVAIL,USE%
TARGET          SOURCE     FSTYPE SIZE  USED AVAIL USE%
/               /dev/sda2  ext4   126G   57G   65G  47%

Illustrative output

TARGET showing / means the data root is on the root filesystem. That is the default and it is the wrong layout for a production host, for one reason: when Docker fills the disk, everything else on the machine fails at the same time.

The layout worth building:

PathFilesystemWhy separate
/RootStays writable when Docker misbehaves
/var/lib/dockerOwn LV or diskImages, layers, build cache β€” the volatile growth
/var/lib/docker/volumes or an external pathOwn LV or diskYour data. Snapshot and back up on its own schedule
/var/logOwn LVContainer logs via journald, and the host journal

Separating volumes from the rest of the data root is the one people skip, and it is what makes a daemon-state rollback possible without a data rollback β€” the constraint from the upgrade rollback lesson.

Inodes: the exhaustion nobody watches

Every overlay layer is a directory tree, and image layers are made mostly of small files. A host with many images consumes inodes far faster than bytes.

Read-only / Safeinode usage
$ df -i /var/lib/docker
Filesystem       Inodes   IUsed    IFree IUse% Mounted on
/dev/sda2       8388608 6710886  1677722   80% /

Illustrative output

80% of inodes used with 47% of bytes used. That host will report β€œdisk fine” on every byte-based check right up until the moment writes start failing with No space left on device β€” the same errno for both conditions, which is why the diagnosis goes wrong so often.

Read-only / Safefind the inode consumer
# Total inodes under each top-level Docker directory
sudo du --inodes -d 1 /var/lib/docker 2>/dev/null | sort -n

# Both dimensions in one alert-friendly line
df -h --output=target,pcent /var/lib/docker | tail -1
df -i --output=target,ipcent /var/lib/docker | tail -1

Discard on SSDs

overlay2 creates and deletes layer directories constantly. Without discard, the SSD firmware never learns those blocks are free, and write amplification and latency climb over months in a way that is easy to blame on the application.

Configuration changeperiodic trim
# Most distributions ship this timer; confirm it is enabled
systemctl status fstrim.timer
sudo systemctl enable --now fstrim.timer

# Trim everything eligible now, verbosely
sudo fstrim -av
Read-only / Safefstrim output
$ sudo fstrim -av
/var/lib/docker: 41.2 GiB (44242247680 bytes) trimmed on /dev/mapper/vg0-docker
/: 8.1 GiB (8697308774 bytes) trimmed on /dev/sda2

Illustrative output

Use the weekly fstrim.timer rather than the discard mount option. Inline discard issues a TRIM on every delete, which on a workload that deletes as many small files as overlay2 does adds latency to exactly the operations you care about. Batch discard once a week does the same job off the hot path.

Moving the data root

The maintenance task with the largest downside if done wrong. The whole operation must happen with the daemon stopped.

Data-loss riskmove data-root
#!/usr/bin/env bash
set -euo pipefail

NEW=/srv/docker

# 1. Stop the socket first, then the service, then containerd
sudo systemctl stop docker.socket docker.service
sudo systemctl stop containerd.service

# 2. Confirm nothing is still holding the tree open
sudo lsof +D /var/lib/docker 2>/dev/null | head

# 3. Copy, preserving EVERYTHING. -a alone is not enough.
sudo mkdir -p "$NEW"
sudo rsync -aHAX --numeric-ids --info=progress2 /var/lib/docker/ "$NEW"/

# 4. Point the daemon at the new location
#    /etc/docker/daemon.json:  { "data-root": "/srv/docker" }
sudo dockerd --validate --config-file /etc/docker/daemon.json

# 5. Start and verify BEFORE deleting anything
sudo systemctl start containerd.service docker.service
docker info --format 'data-root={{.DockerRootDir}}'
docker image ls
docker volume ls
docker ps -a

# 6. Only after verification, and not on the same day:
#    sudo rm -rf /var/lib/docker.old

Filesystem checks

fsck needs the filesystem unmounted, which for a dedicated Docker volume means stopping the daemon. That is a real constraint and it is a further argument for the separate-filesystem layout: you can check and repair the Docker volume without taking the whole host to single-user mode.

Service impact possibleoffline check
sudo systemctl stop docker.socket docker.service containerd.service
sudo umount /var/lib/docker

# ext4: force a check even if the filesystem is marked clean
sudo fsck.ext4 -f -y /dev/vg0/docker

# xfs: repair is a separate tool; xfs_check does not exist
sudo xfs_repair /dev/vg0/docker

sudo mount /var/lib/docker
sudo systemctl start containerd.service docker.service

For xfs, confirm the ftype=1 requirement is satisfied while you are there β€” overlay2 needs it and a filesystem formatted without it will have been failing in confusing ways:

Read-only / Safexfs ftype
sudo xfs_info /var/lib/docker | grep -o 'ftype=[01]'

Knowledge check

Knowledge check Β· 4 questions

  1. Q1. Containers fail to start with "No space left on device" but `df -h` shows 65 GB free. What is the most likely cause?

  2. Q2. Why use the weekly `fstrim.timer` rather than the `discard` mount option for a Docker data root on SSD?

  3. Q3. When copying the data root to a new filesystem, which rsync flags prevent a specific, real failure? Select all that apply.

  4. Q4. Placing volumes on a separate filesystem from the rest of the data root makes it possible to roll back Docker state without rolling back application data.

Passing score: 75%. Answers are checked in this browser.