Reclaiming Disk Space From Docker on Laptops
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.
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
- 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
- Remove images not used by any container.
-aremoves 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
- 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.
- 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"
}
}
}
- 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.
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
- 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
Warn before the disk is full. Add a check to
make doctorthat fails when reclaimable build cache exceeds, say, 30 GB and prints the prune command.Use
docker compose down -vdeliberately when abandoning a project, so its volumes do not linger for months.
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-VHDordiskpart. Pruning alone frees space inside Linux but not onC:.
Apple Silicon (ARM64): multi-arch builds keep separate cache entries for each platform, roughly doubling build cache for projects that build both
linux/arm64andlinux/amd64locally. Build the second architecture in CI instead where possible.
Colima: the disk is a Lima image under
~/.colima/_lima/;colima ssh -- sudo fstrim -areturns 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.