# Clear the Docker build cache, and what each command cannot reach

> Clear docker build cache commands do not all reach the same store. What each prune reclaimed on a GitHub Actions runner, and what clears nothing.

Source: https://latchkey.dev/learn/speed/clear-docker-build-cache  
Updated: 2026-09-20

The command to clear docker build cache is `docker builder prune`, and the reason people run it twice is that it reaches only the builder it is pointed at: on a runner we measured, it reclaimed 180.7 MB from the default builder and exactly 0 B from the `docker-container` builder sitting beside it with 399.6 MB. On a GitHub Actions runner the whole question is usually moot, because a fresh job starts with an empty cache.

The build cache is not inside your images, so deleting images frees none of it, and `docker system df` will happily report tens of gigabytes under a row that `docker image prune` never touches. It has its own commands, and there is more than one of them because there is more than one place the cache can be.

This page is ordered the way the problem actually presents: first where the cache is, then what each command reclaimed when we ran it, then the two commands people expect to clear something that do not.

## On a hosted runner there is usually nothing to clear

A GitHub-hosted or otherwise ephemeral runner gives every job a new machine. We read the store at the top of a job before anything had been built, and every row was zero: no images, no containers, no volumes, and a build cache of 0 B in 0 entries. There is nothing to prune because there is nothing there.

If you are on an ephemeral runner and reading this page because builds are slow, the problem is the opposite of the one the page title describes. Your builds start cold every time, and the fix is a cache backend that outlives the runner rather than a command that removes one. If you are on a self-hosted runner that is reused between jobs, read on, because that machine does accumulate and it will eventually fill its disk.

```docker system df, first step of the job
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          0         0         0B        0B
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B
```

## Read it before you clear it

Two commands, and you want both. `docker system df` gives you the Build Cache row beside images, containers and volumes, so you can see whether the cache is even your problem. `docker buildx du` gives you the same store per builder, which is the reading that matters once more than one builder exists.

On our runner, one build of a three-stage Node image put 15 entries and 180.7 MB into the default builder, of which 109.3 MB was immediately reclaimable and the rest was held because it was still referenced by the image that build produced.

```Terminal
docker system df
docker buildx du
docker buildx du --builder mybuilder   # per builder, once you have more than one
```

## What each prune actually reclaimed

The two forms are not a style choice. Without `-a`, prune removes only the dangling entries, the ones nothing references any more, and that was 109.3 MB of the 180.7 on our runner. With `-a` it takes the rest as well, which was the remaining 71.4 MB, and left the store at 0 B.

Both were fast: 0.424 seconds and 0.134. The reason the first run so often disappoints on a developer machine is exactly this split. On a box that rebuilds one project all day, most of the cache is still referenced by the latest image, so the default form frees the smaller half and you conclude the command does not work.

| Command | Reclaimed | Left behind | Took |
| --- | --- | --- | --- |
| `docker builder prune -f` | 109.3 MB | 71.4 MB | 0.424 s |
| `docker builder prune -a -f` | 71.4 MB | 0 B | 0.134 s |
| `docker buildx prune --builder bl -a -f` | 399.6 MB, all of builder bl | 0 B | 0.656 s |
| `docker buildx rm bl` | the whole builder | nothing of it | 0.199 s |

> Measured by `job-l.sh`, under content/repro/timings/clear-docker-build-cache/, on a Latchkey `latchkey-small` runner on 2026-09-20: 2 vCPU, 7,734 MB RAM, Docker 29.7.2, buildx 0.36.1. One pass per row. The first two rows are sequential on one store, so the second reclaimed what the first left; they are not two measurements of the same thing.

## The cache docker builder prune cannot see

This is the finding worth the page. `docker/setup-buildx-action` creates a builder on the `docker-container` driver, and that builder keeps its cache in a volume of its own rather than in the daemon store. We built once on such a builder and then read both: `docker system df` reported a Build Cache of 0 B, while `docker buildx du` for that builder reported 399.6 MB, sitting in a 349.4 MB local volume.

Then we ran `docker builder prune -a -f` and it reported a total of 0 B reclaimed, with the builder still holding all 399.6 MB. The command was not wrong; it cleared the store it was pointed at, which was empty. Pointing buildx at the builder by name cleared it in 0.656 seconds, and removing the builder outright did the same job in 0.199.

```Terminal
# wrong store: this reclaims 0 B while a buildx builder holds the cache
docker builder prune -a -f

# right store: name the builder
docker buildx prune --builder mybuilder -a -f
docker buildx rm mybuilder
```

> Booting that builder is not free either: the same log shows 5.1 s spent pulling the BuildKit image and creating its container before the build began.

## Two commands that clear nothing

The first is `docker build --no-cache`. It tells one build to ignore what is cached; it deletes nothing, and the build then writes a fresh set of entries on top of the old ones. We measured the store before and after: 180.7 MB became 289.9 MB. If your goal was free space, that command moved you 109.2 MB in the wrong direction.

The second is an `until` filter set too wide. `docker builder prune --filter until=24h` reclaimed 0 B on our runner, correctly, because every entry had been written minutes earlier. Rerunning it as `until=1s` reclaimed 109.1 MB. The filter means "older than", and on a machine that builds constantly the entries you want gone are usually the newest ones.

| What you ran | Store before | Store after | Reclaimed |
| --- | --- | --- | --- |
| `docker build --no-cache` | 180.7 MB | 289.9 MB | none, it added |
| `docker builder prune --filter until=24h -a -f` | 289.9 MB | 289.9 MB | 0 B |
| `docker builder prune --filter until=1s -a -f` | 289.9 MB | 180.7 MB | 109.1 MB |

> Same job, same builder, read with `docker buildx du` between each command. `--no-cache` is still the right flag when you suspect a stale layer; it is simply not a way to free disk.

## Cap it instead of clearing it

On a machine you keep, a size ceiling beats a cron job that deletes everything, because the point of the cache is to still be there tomorrow. Docker garbage collects on its own already: Docker Desktop ships its builder with a keep-storage default of 20 GB, and for a BuildKit builder the documented defaults are a reserved space of 10 percent of the disk or 10 GB, whichever is lower, and a maximum used space of 60 percent of the disk or 100 GB, whichever is lower.

When those defaults are wrong for your disk, set the ceiling rather than pruning on a timer. `docker buildx prune` takes `--max-used-space` and `--min-free-space`, and `docker builder prune` takes `--keep-storage`, so a scheduled job can trim to a number instead of to nothing.

```Terminal
# keep the cache, cap it
docker buildx prune --max-used-space 20GB -f

# older than a week, and only that
docker builder prune --filter until=168h -f
```

## What we ran, so you can disagree with it

One script, `job-l.sh`, run once on a Latchkey `latchkey-small` runner on 20 September 2026, committed under content/repro/timings/clear-docker-build-cache/ with its unedited `job-l.log` and a status file carrying the job id, the runner size, the exit code and the timestamps. Every size on this page is a line of `docker system df` or `docker buildx du` in that log, not an estimate.

The caveats. One pass per command, so the sub-second durations carry runner noise and are worth reading as "fast" rather than as digits. The sizes are specific to one three-stage Node image built from the same fixture as the other scripts in this batch, the one `job-k.log` counts at 414 packages, so treat the shape as the finding and not the megabytes. And the builder comparison used a builder we created in the job, which is what `docker/setup-buildx-action` does for you; a builder that already existed would hold more, not less.

## FAQ

### Why does docker builder prune report 0 B reclaimed?

Usually because the cache is on a different builder. A `docker-container` builder, which is what `docker/setup-buildx-action` creates, keeps its cache in its own volume, and on our runner `docker builder prune -a -f` reported 0 B reclaimed while that builder held 399.6 MB. Run `docker buildx du --builder <name>` to find it, then prune that builder by name.

### Does docker build --no-cache clear the build cache?

No, and it makes the problem worse. It tells one build to ignore the cache, then writes a fresh set of entries alongside what was already there. We measured a store going from 180.7 MB to 289.9 MB across one `--no-cache` build. Use it to force a clean rebuild, and prune separately if the goal is disk space.

### How do I clear the Docker build cache on a GitHub Actions runner?

On a GitHub-hosted runner you do not need to: we read the store at the start of a job and it was 0 B in 0 entries, because the machine is new. If disk is the problem, the space is going to the preinstalled images and toolchains, not to a build cache. On a reused self-hosted runner, prune on a schedule with a size cap rather than after every job.

### What is the difference between docker system prune and docker builder prune?

`docker builder prune` touches only build cache. `docker system prune` also removes stopped containers, unused networks, dangling images, and with `-a --volumes` a great deal more, which is why people report it freeing space they did not expect. If the Build Cache row is the large one, the narrower command is the one you want.

## References

- [Docker Docs: docker builder prune reference (verified 2026-09-20)](https://docs.docker.com/reference/cli/docker/builder/prune/)
- [Docker Docs: docker buildx prune reference (verified 2026-09-20)](https://docs.docker.com/reference/cli/docker/buildx/prune/)
- [Docker Docs: build cache garbage collection (verified 2026-09-20)](https://docs.docker.com/build/cache/garbage-collection/)
- [Docker Docs: build drivers and the docker-container driver (verified 2026-09-20)](https://docs.docker.com/build/builders/drivers/)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
