"No space left on device" during docker build: what to prune and what to keep
Run docker system df to see what’s using the space, then docker builder prune -f. It removes only build cache that nothing uses and leaves images, containers and volumes alone. On 31 October, a docker compose build for Rubric’s pipeline filled its VM’s disk to 100%, and that one command freed 1.44 GB and brought it back to 84%.
See where the space went
docker system df lists images, containers, local volumes and build cache, each with what it calls reclaimable. That day the build cache was 4.6 GB, 1.44 GB of it reclaimable. Here’s the same VM today:
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 4.721GB 3.263GB (69%)
Containers 2 2 116.7MB 0B (0%)
Local Volumes 2 0 621.7MB 621.7MB (100%)
Build Cache 19 0 3.179GB 0B
Reclaimable means no container uses it right now. It doesn’t mean nothing needs it.
The safe one
docker builder prune -f
By default it deletes only dangling build cache, the layers no image refers to any more. -f skips the confirmation. With -a, it deletes all unused build cache, including the layers your next build would have reused, so that build runs npm ci and every step after it again.
The two that delete what the next run needs
Rubric’s jobs are one-shot containers, started by docker compose run --rm and removed when they finish. Between runs no container exists, so Docker counts everything they use as reclaimable.
docker image prune -a deletes every image no container uses, and between runs that includes the pipeline’s own image, the one the next scheduled job starts from.
docker volume prune -a deletes every named volume no container uses. Plain docker volume prune has only removed anonymous volumes since Docker 23. The pipeline keeps a checkout and a login in two named volumes, the 621.7 MB above at 100% reclaimable, and between runs no container holds them. Its database is a bind mount, ./data, which no prune touches. docker system prune leaves volumes alone unless you add --volumes, and even then it takes only anonymous ones.
Make rebuilds cheaper
Order the Dockerfile so a code change reuses the dependency layer:
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts --no-audit --no-fund
COPY src ./src
A change under src/ only rebuilds the layers from COPY src on, and npm ci comes from the cache, the same cache that builder prune -a throws away.
The real fix on this VM was the disk: the root volume was 19 GB on a 40 GB disk, and growing it gave builds 21 GB of room.