Docker Disk Full: Logs, Images and Build Cache, Measured

By Sudipta Dhara / October 10, 2026

By ProDevs · Published 2026-10-10 · Last tested 2026-10-09

AI disclosure: drafted with AI assistance.

Who this is for: developers and DevOps engineers · Intermediate · You’ll need: root on a Linux host running Docker Engine, and 15 minutes.

df -h says / is nearly full, and /var/lib/docker is the biggest directory on the box. On our test VM, one container printing 89.323611 short lines a second wrote 90642495 bytes of log in one hour with no log limit. A neighbour with max-size=10m and max-file=3 peaked at 29854913 bytes. On 27 Sep 2026, a shared build host we run held 749.2 GB of Docker images (555.9 GB reclaimable) and 425.8 GB of build cache.

Short answer: Docker disk space goes to three places: container logs in /var/lib/docker/containers, image layers (overlay2, or /var/lib/containerd on fresh Docker 29 installs) and build cache. Run docker system df to see the split. Log limits set in daemon.json only apply to containers created after the change, so re-create existing ones.

On our test VM, a restart of the daemon and a docker restart both left an old container on {json-file map[]}, with no limit. Only re-creating it applied max-size:10m and max-file:3.

How we tested

  • Box: a disposable test VM, not a production server. 2 vCPU, 4096229376 bytes of RAM (Docker prints 3.815GiB), a 42949672960-byte disk with a 40483942400-byte root filesystem. Ubuntu 24.04.3 LTS, kernel 6.8.0-90-generic, aarch64.
  • Docker: Docker CE 29.8.2 from Docker’s apt repository, pinned and held. Compose v5.6.0, Buildx v0.37.1, containerd 2.3.6. Logging driver json-file. Storage driver overlayfs, driver-type io.containerd.snapshotter.v1.
  • Dates: log growth and the re-create trap on 2026-10-05 (IST). Image and build-cache prunes on 2026-10-09, 15:29:57 to 15:30:18 IST.
  • Units: MB below means 1,000,000 bytes, as the run recorded it. Strings such as 472.8MB are what Docker printed.

Where did my disk go?

Docker’s disk use lands in three places on a single host.

  1. Container logs. The default json-file driver writes one file per container. Docker’s docs say that by default no log rotation is performed, so a container with a lot of output can fill the disk (Docker docs: configure logging drivers). On a json-file host the files sit under /var/lib/docker/containers.
  2. Image layers. On Docker Engine 29.0 and later, fresh installs use the containerd image store. Upgraded installs keep the legacy overlay2 driver until you switch (Docker docs: containerd image store). The same page says containerd uses a separate storage path from the Docker data directory, and that it stores images both compressed and uncompressed, so it uses more disk for the same images.
  3. Build cache. BuildKit keeps cache from every build until you prune it or garbage collection removes it (Docker docs: build garbage collection).

Our fresh 29.8.2 install matched the docs. /var/lib/docker/overlay2 did not exist. After the first alpine:3.20 pull, the image snapshots were under /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs. If you search for “overlay2 full” on a new install, look at /var/lib/containerd as well.

Volumes are a fourth place. They hold data you meant to keep, so they’re out of scope here. See Docker volume or bind mount. Disk and memory fill up for different reasons. For RAM, see setting a Docker memory limit.

Prerequisites: what do you need first?

  • Root on the host. The du, systemctl and daemon.json steps need it, so the commands below use sudo.
  • Docker Engine on Linux. We tested 29.8.2. Check yours with docker version.
  • A little free space, so the daemon can still write while you clean up.
  • A note of who owns each container, so you know which ones you may re-create. Docker on a VPS shares the host disk with everything else on the machine.

The commands in the steps below need root, so run them with sudo where shown.

Step 1: Where is the space going?

Measure before you delete anything.

# tested on Docker Engine v29.8.2
df -h /
docker system df
docker system df -v
sudo du -sh /var/lib/docker/containers /var/lib/docker/overlay2 /var/lib/containerd 2>/dev/null
docker info -f '{{ .DriverStatus }}'

docker system df prints one row each for images, containers, local volumes and build cache, with TOTAL, ACTIVE, SIZE and RECLAIMABLE columns. -v breaks each row down by object (Docker docs: docker system df). The DriverStatus line tells you which image store you’re on. Ours printed driver-type io.containerd.snapshotter.v1.

To find the biggest container logs, print each log path, then size it:

# tested on Docker Engine v29.8.2
docker ps -aq | xargs docker inspect -f '{{.Name}} {{.LogPath}}'
sudo du -h /var/lib/docker/containers/*/*-json.log*

What our VM showed at each point:

Point docker system df Root filesystem used (bytes)
One image (alpine:3.20), no build cache Images 1, 13.67MB, reclaimable 0B. Build cache 0 3031937024
After pulling python:3.12-alpine and postgres:16-alpine Images 3, 500MB, reclaimable 472.8MB 3518783488
After five builds of a small Dockerfile Images 4, 500MB. Build cache 8 entries, 13.65MB 3529269248

Can’t remember the inspect format string? Talk2Term turns a plain-English request into a shell command and asks before it runs anything.

Step 2: How do I cap container logs, including on old containers?

Set a default in /etc/docker/daemon.json. Docker’s docs say log-opts values must be strings, so "3" needs quotes (Docker docs: JSON File logging driver).

{"log-driver":"json-file","log-opts":{"max-size":"10m","max-file":"3"}}
# tested on Docker Engine v29.8.2
sudo systemctl restart docker
docker inspect -f '{{.HostConfig.LogConfig}}' <container>

For json-file, max-size defaults to -1 (unlimited), max-file defaults to 1 and only works when max-size is set, and compress defaults to false (same JSON File page). The local driver is the alternative. It keeps 100MB per container by default (20M per file, 5 files) and compresses (Docker docs: local logging driver). We didn’t measure the local driver.

What did the limit do to log growth?

Two alpine:3.20 containers ran the same loop for 3600 seconds on 2026-10-05, from 09:54:16 to 10:54:16 IST. One had no log options. One had --log-opt max-size=10m --log-opt max-file=3.

# tested on Docker Engine v29.8.2
docker run -d --name log-nolimit alpine:3.20 sh -c 'i=0; while :; do printf "%s %0200d\n" "$(date +%s)" "$i"; i=$((i+1)); usleep 10000; done'
docker run -d --name log-capped --log-opt max-size=10m --log-opt max-file=3 alpine:3.20 sh -c 'i=0; while :; do printf "%s %0200d\n" "$(date +%s)" "$i"; i=$((i+1)); usleep 10000; done'
Container Log bytes at 0 s Log bytes at 3600 s Peak Files at end
log-nolimit 2820 90645315 90645315 1, none rotated
log-capped 1128 20642445 29854913 at 1980 s 3, 2 rotated (10000078 and 10000210 bytes)

log-nolimit grew by 90642495 bytes (90.642495 MB) in the hour. docker logs counted 321565 lines, which is 89.323611 lines per second. The capped container’s docker logs returned 73305 lines, because only the retained files are left to read.

Why didn’t my new limit apply? The re-create trap

This is the part that catches people. We started trap-demo with no log options, then changed daemon.json and restarted the daemon. Here is what docker inspect -f '{{.HostConfig.LogConfig}}' printed at each point on 2026-10-05 at 11:14 IST. The labels on the left are ours. The values on the right are the exact output:

trap-demo, before the daemon.json change:     {json-file map[]}
trap-demo, after systemctl restart docker:    {json-file map[]}
trap-demo, after docker restart trap-demo:    {json-file map[]}
trap-new, created after the change:           {json-file map[max-file:3 max-size:10m]}
trap-demo, after docker rm -f and docker run: {json-file map[max-file:3 max-size:10m]}

trap-demo had restart policy no, so it stayed exited after the daemon restart until we started it. docker restart did not pick up the new limit. Re-creating it did. That matches Docker’s docs: existing containers keep the logging options they were created with, and the container has to be re-created (Docker docs: configure logging drivers).

Re-creating removes the old log. The old trap-demo log was 294014 bytes, and it was gone after docker rm -f. Over the next 3600 seconds, the re-created trap-demo peaked at 29856246 bytes and trap-new at 29859825 bytes, both at 1980 s, with 3 files each.

To re-create with Compose, run docker compose up -d --force-recreate (Docker docs: docker compose up). For one service, set the limit in the file (Compose file reference: services):

services:
  app:
    image: alpine:3.20
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

Warning: Don’t truncate or delete json log files by hand. Docker’s docs say these files are for the daemon only, and outside tools can interfere with Docker’s logging (Docker docs: JSON File logging driver).

Step 3: How do I remove unused images safely?

Docker cleans up conservatively and leaves unused objects until you ask (Docker docs: prune unused Docker objects).

# tested on Docker Engine v29.8.2
docker image prune
docker image prune -a
docker image prune -a --filter "until=24h"
docker container prune

docker image prune removes only dangling images, the ones with no tag and no container. -a removes every image that no existing container uses. docker container prune removes stopped containers, whose writable layers still take space (same pruning page). We passed -f because our session had no terminal for the prompt.

On our VM, docker image prune -f printed Total reclaimed space: 0B, because no image was dangling. docker image prune -a -f printed Total reclaimed space: 486.4MB. Images went from 4 to 1, and df -h went from 3.3G used to 2.9G. alpine:3.20 stayed, because stopped containers still used it.

What does RECLAIMABLE mean? Docker’s system df reference doesn’t define the column. On our VM it read 472.8MB when two of the three images had no container, and the later image prune -a printed 486.4MB. Read RECLAIMABLE as a guide, and trust the line the prune prints.

Don’t run rm -rf on /var/lib/docker/overlay2 or /var/lib/containerd. Docker tracks those files itself. Remove images and containers with the prune commands.

Step 4: How do I clear the Docker build cache?

# tested on Docker Engine v29.8.2
docker buildx du
docker builder prune
docker builder prune -a
docker builder prune --filter until=24h

docker builder prune removes dangling cache. -a removes all unused build cache (Docker docs: docker builder prune). BuildKit also runs garbage collection on its own, by age and size policy (Docker docs: build garbage collection).

We built a three-line Dockerfile five times with a cache-busting build argument:

FROM alpine:3.20
ARG BUST=0
RUN echo "$BUST" > /bust.txt

docker buildx du printed a total of 13.6MB, 13.62MB, 13.63MB, 13.64MB and 13.65MB after builds 1 to 5. Then:

Command What it printed Build cache after
docker builder prune -f Total: 74.26kB 1 entry, 13.58MB, reclaimable 0B
docker builder prune -a -f Total: 13.58MB 0

Builder prune printed Total:, not Total reclaimed space:, on this version. We didn’t test whether docker builder du also works.

Step 5 (optional): Can one command do all of this?

docker system prune removes stopped containers, unused networks, dangling images and unused build cache. Volumes are not pruned unless you add --volumes, and unused tagged images stay unless you add -a (Docker docs: prune unused Docker objects).

# tested on Docker Engine v29.8.2
docker system prune

It asks for confirmation first. Read the list it prints before you answer. We didn’t run docker system prune in this test.

Verify: did the space come back?

# tested on Docker Engine v29.8.2
docker system df
df -h /
docker inspect -f '{{.HostConfig.LogConfig}}' <container>

Check every re-created container shows {json-file map[max-file:3 max-size:10m]}, or the limit you set. After all four prunes, our VM showed:

  • build cache 0, and /var/lib/containerd at 14400345 bytes
  • root filesystem used at 3033055232 bytes, against 3529269248 after the builds
  • the container log directory still at 190069400 bytes

That last line is the point. Image and cache prunes don’t remove containers, so they don’t free container logs. Logs come back only when you cap and re-create, or remove the containers.

How big can it get?

The two sets of numbers on this page come from different machines.

  • Our test VM (2026-10-05): one container at 89.323611 lines per second added 90642495 bytes of log in an hour with no limit. With max-size=10m and max-file=3, the peak was 29854913 bytes.
  • A shared build host we run (27 Sep 2026): 749.2 GB of Docker images (555.9 GB reclaimable) and 425.8 GB of build cache, as published in our self-hosted runners tutorial. Not all of it came from CI.

Troubleshooting: why is the disk still full?

Symptom Likely cause Fix
Log keeps growing after the daemon.json change The container was created before the change Re-create it. docker restart kept {json-file map[]} on our VM
docker system df shows little, but the disk is full containerd store, volumes or non-Docker files Run du on /var/lib/containerd, the volumes and the rest of /
Space not freed after image prune -a A stopped container still uses the image Run docker container prune first, then prune images
Daemon won’t start after a daemon.json edit Invalid JSON, or a numeric value not in quotes Fix the file, then check journalctl -u docker
Container exits with code 137 That’s a kill signal, not a full disk See the memory limit guide

Clean up: how do I undo the test?

# tested on Docker Engine v29.8.2
docker rm -f log-nolimit log-capped trap-demo trap-new
docker image rm alpine:3.20 python:3.12-alpine postgres:16-alpine
sudo rm /etc/docker/daemon.json
sudo systemctl restart docker

Only remove daemon.json if the test created it. On our VM it didn’t exist before the run. If yours already had settings, put the old file back instead.

FAQ

Why is /var/lib/docker so big?
Container logs, image layers and build cache, plus volumes. Run docker system df -v to see each. On fresh Docker 29+ installs, image data sits under /var/lib/containerd.

How do I limit log size for a container that already exists?
Set max-size and max-file in daemon.json or on the container, then re-create it. A daemon restart alone doesn’t change existing containers. On our test VM, docker restart didn’t change it either. Only re-creating applied the new limit.

Can I delete files in /var/lib/docker/overlay2 by hand?
No. Remove images and containers with docker image prune, docker container prune or docker system prune.

How do I clear Docker build cache?
docker builder prune. Add -a to remove all unused cache, not just dangling. BuildKit also garbage-collects by policy. On our test VM, docker builder prune -a -f printed Total: 13.58MB and left the build cache at 0.

If the disk fills again

This guide measures logs, images, and build cache. If the disk fills again after that measurement, send the docker system df lines.

Use the contact form. We reply in writing, usually within one business day. The reply says we can help, we should not take this on, or we need one detail. That look is free. If we are full, we say so. Do not send passwords or keys.

If that reply says a paid written audit is the next step, the range is ₹15,000–₹25,000. That is the range on the services page. This article has no payment link.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top

IP Geolocation by DB-IP