Disk is full, and prune returned two hundred megabytes
The familiar scenario: monitoring yells about 95% on the root partition, docker system prune -f runs briskly, reports freeing a couple hundred megabytes — and everything remains exactly as it was. After an hour the partition runs out completely, Docker stops starting containers, and df -h shows /var/lib/docker weighing 40 gigabytes.
It’s not that the command is broken. It does exactly what the documentation says — and almost never what people expect from it. Below is a map of what actually makes up these 40 gigabytes, and where each part comes from.
docker system prune without flags removes only: stopped containers, unused networks, dangling images, and unused build cache. It does not touch the logs of running containers or volumes — and that is where the main volume usually lies.
Before deleting anything — check where the space actually is
There is only one entry point:
docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 16.43MB 11.63MB (70%)
Containers 2 0 212B 212B (100%)
Local Volumes 2 1 36B 0B (0%)
You should read the RECLAIMABLE column: this is the upper bound of what can actually be freed. If the total SIZE across all three lines is noticeably smaller than the real size of /var/lib/docker, it means the space was consumed by things not included in this table. And logs of containers are exactly such items. The -v flag expands each line for individual images, containers, and volumes with columns SHARED SIZE and UNIQUE SIZE — useful to understand which exact image is pulling the weight.
Column “system prune” shows the behavior of the command without additional flags. Based on the Docker documentation.
Logs that nobody rotates
The default logging driver is json-file, and its defaults look like: max-size = -1 (unlimited), max-file = 1, compress = false. Moreover, max-file doesn’t work at all until you set max-size. So out of the box there is no rotation: a chatty container writes one file that grows until the partition runs out.
To check how much this costs:
du -sh /var/lib/docker/containers/*/*-json.log | sort -h | tail -5
Fix it in /etc/docker/daemon.json:
{
"log-driver": "local",
"log-opts": { "max-size": "20m", "max-file": "5" }
}
The local driver documentation explicitly recommends: it rotates logs by default (20 MB × 5 files, compression enabled) and uses a more compact format. The trade-off is that docker logs works, but third-party log collectors reading *-json.log directly will break. If you have such collectors, keep json-file, but with explicit max-size/max-file.
Next are the gotchas everyone runs into. Changing the driver in daemon.json affects only containers created after the change; restarting the daemon does not affect existing containers. Check the actual driver for a specific container:
docker inspect -f '{{.HostConfig.LogConfig.Type}}' имя_контейнера
If it still shows json-file without options — the container must be recreated (docker compose up -d --force-recreate), not restarted. Resetting an inflated log file right now can be done like this:
truncate -s 0 $(docker inspect -f '{{.LogPath}}' имя_контейнера)
This is a one-off measure: without changing the config, the file will grow again.
Build cache lives by its own rules
If something is being built on the host — CI runner, docker compose build, — the BuildKit cache becomes the second-largest contender. docker system prune cleans it, but only unused items; a separate command gives control by age:
docker buildx prune --filter until=72h
Automation is configured by the builder.gc section in daemon.json. The docs describe four default policies: ephemeral cache is removed after 48 hours, any unused one after 60 days, and the rest is limited by defaultKeepStorage (for Docker Desktop it expands to 20 GB). For a server, the docs do not fix a default, so it’s better to set a limit manually — otherwise the cache will push against disk size rather than your number.
Volumes and layers: the things prune ignores
Volumes are not deleted by default intentionally — they are data. The --volumes flag adds to cleanup only anonymous volumes; named volumes remain. You can remove them via docker volume prune --all, but first make sure there isn’t a volume with Immich database or Home Assistant configuration in the list. docker compose down does not touch volumes — only down -v kills them.
The final source is the writable layer of the containers themselves. An application that writes caches or downloads not to the container’s filesystem but inside the container silently grows the overlay2 layer. You can see it like this:
docker ps -s
The first number in the SIZE column is the writable layer data, in parentheses labeled virtual — together with the read-only layers of the image. If the writable layer of a container measures in gigabytes, the problem is not Docker, but that the directory was forgotten to be given a volume. And only if after all this there is still not enough space — docker system prune -a will remove unused images (not only dangling, but all images not referenced by any container).
Forum reference on the topic: Acceptance of a new VPS within the first hour: 12 checks, while the money-back period is active and GitHub lay idle for 7 hours 35 minutes: three forks that rise by evening.
Sources
- Configure logging drivers — Docker Docs
- JSON File logging driver — Docker Docs
- Local file logging driver — Docker Docs
- Prune unused Docker objects — Docker Docs
- Build cache garbage collection — Docker Docs
- docker system df — Docker Docs
What turned out to be the main space eater on your Docker host — logs, build cache, or forgotten volumes? And who has already switched to the local driver: were there issues with log collectors?
