{"slug": "docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune", "title": "docker builder prune freed 0 B: two places Docker keeps disk space that no prune reaches", "summary": "A developer documented two cases where Docker's prune commands report zero bytes reclaimed while disk usage keeps growing, measured on Docker Engine 29.9.0. The first is build cache held in the buildx_buildkit_<node>_state volume, which `docker builder prune` never touches because it targets the selected builder rather than the docker-container driver's node; `docker buildx prune --builder <name>` reclaimed 73.36MB in the test. The second is overlay2 image and container data left hidden on disk after a daemon is switched to the containerd image store, where `docker system prune -af` also reports 0B while /var/lib/docker/overlay2 held 71.8MB.", "body_md": "*Disclosure: this article was drafted with AI assistance (Claude) from measurements recorded in the dockdu repository. Every number comes from [the measurement log](https://github.com/maximilianfeix/dockdu/blob/main/docs/verification/buildx-overlay2-2026-10-15.md) and can be reproduced on a disposable `docker:dind` container.*\n\n`docker system df` says the build cache is empty. `docker system prune -af` reports `Total reclaimed space: 0B`. `df -h` still shows the disk filling up. Here are two cases where all three statements are true at once, both measured on Docker Engine 29.9.0, with the commands to check each by hand.\n\n`docker buildx create --driver docker-container` starts BuildKit in its own container, `buildx_buildkit_<node>`. The builder keeps its whole state, including its build cache, in a volume named `buildx_buildkit_<node>_state` ([buildx source](https://github.com/docker/buildx/blob/master/driver/docker-container/driver.go)). The node is usually the builder name plus `0`.\n\n**Measured:** one builder `ci`, one build that added 30 MB.\n\n``` bash\n$ docker system df\nTYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE\nImages          2         1         430.9MB   73.34MB (17%)\nContainers      1         1         40.96kB   0B (0%)\nLocal Volumes   2         1         73.31MB   114.7kB (0%)\nBuild Cache     0         0         0B        0B\n```\n\nThe 73 MB of build cache is in the **Local Volumes** row. Then the two prune commands, with `du -sk` on the state volume after each:\n\n| Command | State volume on disk | Prune printed | \n|---|---|---|\n| (before) | 72452 KiB |  | \n| `docker builder prune -af` | 72452 KiB | `Total: 0B` | \n| `docker buildx prune --builder=ci -af` | 740 KiB | `Total: 73.36MB` | \n\n`docker builder prune` works on the *selected* builder, which here was the daemon's own. A CI host that once ran `docker buildx create --use` and was later switched back keeps growing in a volume that `docker builder prune` never touches.\n\nThere is one more trap. `docker buildx rm --keep-state` removes the builder but keeps the volume. Nothing then refers to it, so it only shows up as an unused volume.\n\n**Check by hand:**\n\n```\ndocker volume ls --filter name=buildx_buildkit_      # one per docker-container node\ndocker system df -v | grep buildx_buildkit_          # sizes\ndocker buildx ls                                     # which builder each node belongs to\ndocker buildx du --builder <name>                    # that builder's cache, record by record\n```\n\n**Free it:** `docker buildx prune --builder <name>`. Add `--keep-storage 10gb` to keep a working set. Or set a GC policy in that builder's `buildkitd.toml`.\n\nDocker Desktop and fresh Engine 29 installs use the containerd image store. If a daemon is switched to it, the images and containers of the classic overlay2 store are not deleted. Docker's docs say they become hidden: [\"Your overlay2 data remains on disk but becomes hidden.\"](https://docs.docker.com/engine/storage/containerd/)\n\n**Measured:** one data root with `alpine:3.22` and `nginx:1.29-alpine` pulled under the graph driver, then the same data root started with `--feature containerd-snapshotter=true`:\n\n``` bash\n$ docker images                 # (empty)\n$ docker system df              # every row 0B\n$ docker system prune -af\nTotal reclaimed space: 0B\n$ sudo du -sh /var/lib/docker/overlay2\n71.8M   /var/lib/docker/overlay2\n```\n\nOn a real machine that number is whatever you had before the switch, often many gigabytes.\n\n```\ndocker info --format '{{.Driver}} {{json .DriverStatus}}'    # io.containerd.snapshotter.v1 = containerd store\nsudo ls /var/lib/docker/image/overlay2/layerdb/sha256 | wc -l  # layers the classic store still records\nsudo du -sh /var/lib/docker/overlay2\n```\n\n**Free it:** switch the daemon back to overlay2. The old images and containers reappear and can be removed normally. `docker save` anything you want to keep first.\n\nA common suspicion is that the containerd store's second copy of each layer (compressed blobs next to unpacked snapshots) is space `df` ignores. It is not, at least on 29.9:\n\n``` bash\n$ docker system df     → Images 296.5MB\ncontent (compressed)     72.4 MB\nsnapshots (unpacked)    224.4 MB      sum 296.8 MB\n```\n\nThe Images row includes both.\n\nThe third classic case, [moby#47089](https://github.com/moby/moby/issues/47089): `overlay2/` is far larger than images, containers and build cache together. The graph driver records which directory belongs to which image layer (`image/overlay2/layerdb/sha256/*/cache-id`) and to which container (` layerdb/mounts/*/mount-id`). A directory that appears in neither is a BuildKit snapshot or a leftover. Comparing the size of those directories with the private build cache shows whether leftovers are the likely cause. Don't delete them by hand while the daemon runs.\n\n[dockdu](https://github.com/maximilianfeix/dockdu), a read-only CLI that explains Docker disk usage, does all three checks since 0.4.0:\n\n`dockdu --disk` splits `overlay2/` into image layers, container layers and directories with no record.\nIt never deletes anything, and its Docker client refuses any request other than GET/HEAD on four read endpoints. The manual commands above work without it.", "url": "https://wpnews.pro/news/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune", "canonical_source": "https://dev.to/taventix/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune-reaches-3lpb", "published_at": "2026-10-15 05:33:22+00:00", "updated_at": "2026-10-15 05:51:20.683114+00:00", "lang": "en", "topics": ["developer-tools", "mlops"], "entities": ["Docker", "BuildKit", "Docker Engine", "containerd", "overlay2", "Claude", "dockdu"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune", "markdown": "https://wpnews.pro/news/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune.md", "text": "https://wpnews.pro/news/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune.txt", "jsonld": "https://wpnews.pro/news/docker-builder-prune-freed-0-b-two-places-docker-keeps-disk-space-that-no-prune.jsonld"}}