A build that worked yesterday fails with failed to copy files: no space left on device or ERROR: failed to solve: write /var/lib/docker/...: no space left on device, Postgres refuses writes with could not extend file, and yet Finder or Explorer reports plenty of free space on the laptop. The space is gone inside the Linux VM's disk image, which grows with every image, build and volume and almost never shrinks by itself. This page shows how to find what is using it, reclaim it without deleting anyone's local database, and keep it from filling up again. It belongs to choosing and tuning a local container runtime.

On a laptop that builds images daily, Docker's disk commonly reaches 60–120 GB within a few months. Most of that is build cache that could be regenerated in minutes.

Diagnostic

Break usage down by category and find the largest single items:

#!/usr/bin/env bash
set -euo pipefail
docker system df
echo "--- largest images"
docker image ls --format '{{.Size}}\t{{.Repository}}:{{.Tag}}' | sort -h -r | head -5
echo "--- volumes"
docker system df -v | awk '/VOLUME NAME/,/^$/' | sort -k3 -h -r | head -6
echo "--- VM disk file"
ls -lh "$HOME/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw" 2>/dev/null || true
du -h "$HOME/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw" 2>/dev/null || true

Expected bad output:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          87        9         41.2GB    35.8GB (86%)
Containers      14        5         1.3GB     1.1GB (84%)
Local Volumes   23        4         9.7GB     6.2GB (63%)
Build Cache     1164      0         48.9GB    48.9GB
--- VM disk file
-rw-r--r--  1 dev  staff  128G Sep 12 09:14 Docker.raw
 102G	Docker.raw

Build cache and unused images account for about 85 GB. ls shows the file's maximum size; du shows the space it actually occupies on the host — 102 GB.

Docker Disk Usage by Category Bar chart of reclaimable disk usage for build cache, images, volumes and containers on one laptop. Docker Disk Usage by Category build cache 48.9 GB unused images 35.8 GB unused volumes 6.2 GB stopped containers 1.1 GB
Build cache and unused images are almost always the bulk; volumes are small but hold state.

Root cause

Every docker build adds layers to the BuildKit cache, and every pulled or rebuilt tag leaves the previous image behind as a dangling or unreferenced image. BuildKit has a garbage collector, but its default keep-storage threshold is large enough that it rarely triggers on a laptop. Volumes accumulate from projects you ran once, because docker compose down keeps named volumes unless -v is passed. Finally, the VM's disk image is sparse: it grows on demand, but when files inside are deleted, the host only gets the space back if the VM passes a discard (TRIM) through to the image file. Recent Docker Desktop versions do that automatically on macOS; WSL2's ext4.vhdx does not shrink unless compacted.

Resolution

  1. Prune build cache older than a week. It is fully regenerable, and a week keeps the cache for the projects you are actively working on:
#!/usr/bin/env bash
set -euo pipefail
docker builder prune --filter until=168h --force
  1. Remove images not used by any container. -a removes all unused images, not just dangling ones; they are pulled or rebuilt again on demand:
#!/usr/bin/env bash
set -euo pipefail
docker container prune --force
docker image prune -a --filter until=168h --force
  1. Review volumes before removing any. List volumes not attached to a container along with their Compose project, then remove only those from projects you no longer run:
#!/usr/bin/env bash
set -euo pipefail
for v in $(docker volume ls -qf dangling=true); do
  printf '%s\t%s\n' "$v" "$(docker volume inspect -f '{{index .Labels "com.docker.compose.project"}}' "$v")"
done
# after review, remove a specific old project's volumes:
# docker volume ls -q --filter label=com.docker.compose.project=oldproject | xargs -r docker volume rm

Never run docker system prune --volumes as a routine command: it deletes every unattached volume, including the local database of any project that happens to be stopped.

  1. Cap the build cache permanently with a BuildKit garbage-collection policy in the daemon config (Settings → Docker Engine in Docker Desktop):
{
  "builder": {
    "gc": {
      "enabled": true,
      "defaultKeepStorage": "20GB"
    }
  }
}
  1. Return the space to the host. On macOS with a current Docker Desktop, it happens automatically within minutes. On WSL2, compact the virtual disk after pruning:
wsl --shutdown
Optimize-VHD -Path "$env:LOCALAPPDATA\Docker\wsl\disk\docker_data.vhdx" -Mode Full

Optimize-VHD needs the Hyper-V PowerShell module; on Windows Home, run diskpart with select vdisk file=... and compact vdisk instead.

Is This Data Safe to Prune? Decision diagram classifying Docker data as safe, review first, or never prune routinely. Is This Data Safe to Prune? What kind of data is it? Build cache, images safe: regenerable Stopped containers safe unless debugging Named volumes review: may hold a DB
Only volumes hold state that cannot be regenerated; everything else is cache.

Expected output

$ docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          11        9         6.1GB     0.9GB (14%)
Containers      5         5         0.2GB     0B (0%)
Local Volumes   6         4         3.6GB     0.1GB (2%)
Build Cache     212       0         7.4GB     7.4GB
$ du -h ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
 19G	Docker.raw

Active images and the volumes of current projects remain, the build cache is back to a working set, and the host has about 80 GB back.

Prevention

  1. Schedule the safe prunes. A weekly job on each laptop keeps the cache bounded without anyone thinking about it:
#!/usr/bin/env bash
set -euo pipefail
( crontab -l 2>/dev/null | grep -v docker-weekly-prune
  echo '0 12 * * 1 /usr/local/bin/docker builder prune --filter until=168h -f >/dev/null 2>&1 # docker-weekly-prune' ) | crontab -
crontab -l | grep docker-weekly-prune
  1. Warn before the disk is full. Add a check to make doctor that fails when reclaimable build cache exceeds, say, 30 GB and prints the prune command.

  2. Use docker compose down -v deliberately when abandoning a project, so its volumes do not linger for months.

How Docker Disk Usage Grows Without Pruning Timeline of VM disk size over six months on a laptop that builds daily and never prunes. How Docker Disk Usage Grows Without Pruning Week 1 14 GB after first setup Month 1 38 GB, cache dominant Month 3 71 GB, old images pile up Month 6 102 GB, build fails
Growth is steady and silent until a build fails; a weekly prune keeps it near the first point.

Platform caveats

macOS (Docker Desktop): the disk image limit is set under Settings → Resources → Advanced → Virtual disk limit. Lowering it below current usage is refused; prune first, then lower it.

WSL2: the data disk grows to the size of the largest usage ever and stays there until compacted with Optimize-VHD or diskpart. Pruning alone frees space inside Linux but not on C:.

Apple Silicon (ARM64): multi-arch builds keep separate cache entries for each platform, roughly doubling build cache for projects that build both linux/arm64 and linux/amd64 locally. Build the second architecture in CI instead where possible.

Colima: the disk is a Lima image under ~/.colima/_lima/; colima ssh -- sudo fstrim -a returns freed blocks to the host.

Rollback

Pruned cache and images cannot be restored, but they are rebuilt or pulled on the next docker compose up --build — the only cost is one slower build. A volume removed by mistake is recoverable only from a dump, which is why volume removal is always a reviewed, manual step:

#!/usr/bin/env bash
set -euo pipefail
docker compose pull
docker compose build
docker compose up -d

Frequently Asked Questions

Is docker system prune -a safe to run?

Without --volumes, it removes stopped containers, unused networks, unused images and build cache — all regenerable — so it is safe apart from forcing slower builds and pulls afterwards. With --volumes it also deletes unattached named volumes, which may hold local databases.

Why did the host not get space back after pruning?

The VM's disk image must release freed blocks to the host. Docker Desktop on macOS does this automatically after a short delay. WSL2 requires wsl --shutdown followed by compacting the .vhdx file; Colima requires fstrim inside the VM.

How big should the build cache be allowed to grow?

20 GB is a sensible cap for most laptops. It holds the working set for several active projects while keeping the total disk footprint predictable. Set it with builder.gc.defaultKeepStorage in the daemon configuration.

Why are there hundreds of <none> images?

They are previous builds of a tag that was rebuilt; the tag moved to the new image and the old one became dangling. docker image prune removes them, and they reappear with every rebuild unless the build cache policy and a weekly prune keep them in check.