Repository navigation
Docker Disk Maintenance
Stock commands only — nothing custom to install. Background and the why live in Incident: Docker Disk Exhaustion (2026-09-07).
# 1. Real free space (df -h / reports the read-only system snapshot, ignore it)
df -h /System/Volumes/Data
# 2. How big is Docker really? (ls shows the sparse virtual max, du shows actual)
du -sh ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
# 3. What inside Docker is using it
docker system dfdocker image prune -f # dangling images only - safe, offsets daily :latest pulls
docker builder prune -f # build cache beyond the keep-storage in daemon.json
docker container prune -f # STOPPED containers only - check nothing was killed mid-workDocker.raw auto-TRIMs after a prune, so the file shrinks on its own. No manual
fstrim needed on current Docker Desktop.
Usually the single largest item (23.5 GB when last measured). Nothing prunes it.
docker system df -v | grep -i vscode # check its size firstClosing VS Code is not enough, and neither is stopping the containers.
docker volume rm blocks on any container that references the volume, running
or not — the containers must be removed. Expect this error otherwise:
Error response from daemon: remove vscode: volume is in use - [<ids>]
Full procedure:
# 1. Which containers reference it
docker ps -a --filter volume=vscode --format '{{.Names}}\t{{.State}}'
# 2. Stop any that are running. Note: devcontainers with
# restart: unless-stopped come back by themselves and stay up even with
# VS Code closed.
docker stop <names>
# 3. Remove them. NEVER add -v: that would delete the named volumes too,
# including *-claude-config (credentials) and *-pgdata (databases).
docker rm <names>
# 4. Now the volume will go
docker volume rm vscodeSafe to do because devcontainer source is bind-mounted from the host and state
lives in named volumes, both of which survive docker rm. What you lose is the
container writable layer (mise/bun caches) — rebuilt on next "Reopen in
Container", along with a one-time VS Code server re-download.
Do this when the volume exceeds ~10 GB, roughly quarterly.
Dev Containers: Clean Up Dev Containers…Dev Containers: Clean Up Dev Volumes…
Listed so you recognise them, not so you run them. Both appear constantly in answers about reclaiming Docker space. Deliberately not written as a copyable block — every other command on this page is safe to paste, and these must not look like the next step.
| Command | What it takes with it |
|---|---|
docker system prune with -a --volumes
|
Every volume not attached to a running container: *-pgdata, *-claude-config, *-fish-data. Databases and credentials. |
docker volume prune with -a
|
The same volumes, without even reclaiming images. |
"Not attached to a running container" sounds narrow and isn't. Stop a devcontainer, run either, and its database and stored credentials are gone.
To release a retired project's volumes deliberately: docker compose down -v
in that project's folder. That is scoped to the one project you are finished
with.
APFS local snapshots pin the freed blocks — copy-on-write means deleting data
inside Docker.raw returns nothing to the pool while a snapshot references it.
tmutil listlocalsnapshots / # same-day snapshots are the usual causeThey expire on their own within ~24h and the space returns. To force it (this
is what macOS itself runs under pressure, 4 = urgency):
tmutil thinlocalsnapshots / 30000000000 4Deleting local snapshots does not affect Time Machine backups on an external or network disk.
Symptom: docker ps never returns (rather than erroring). Usually means the
Linux VM died but the host-side backend still holds the socket.
# Confirm it
grep -iE 'no space|GET /error' \
~/Library/Containers/com.docker.docker/Data/log/host/com.docker.backend.log | tail
# Recover
osascript -e 'quit app "Docker Desktop"'
pgrep -f 'com\.docker\.back[e]nd' # note the PID, then: kill -9 <pid>
open -a Docker
until docker info >/dev/null 2>&1; do sleep 5; done; echo "daemon up"Note the back[e]nd bracket trick: a plain pkill -f "com.docker.backend"
matches the killing shell's own command line and kills itself instead.
After a crash, docker container prune -f is not safe. Containers that were
running are now Exited (255) and indistinguishable from long-idle ones.
Separate them by stop time before pruning:
docker inspect -f '{{.State.Status}}|{{.State.FinishedAt}}|{{.Name}}' $(docker ps -aq) | sort -t'|' -k2Crash-killed containers all share the daemon-boot timestamp.
Disk usage limit — Docker Desktop → Settings → Resources → Advanced. Set it below your typical free space (64 GB here). Without it the default is a 1 TB virtual disk, i.e. Docker can consume the whole Mac. Reducing the limit recreates the disk and destroys all volumes — back up first.
Log rotation — ~/.docker/daemon.json:
{
"builder": { "gc": { "enabled": true, "defaultKeepStorage": "5GB" } },
"log-driver": "local"
}Every line a container writes to stdout/stderr is stored on disk, and the default
json-file driver never rotates unless you tell it to — its max-size
defaults to -1 (unlimited) with max-file: 1. A chatty dev server or a
database logging every query grows that file forever, and it shows up nowhere in
docker system df.
json-file (default) |
local |
|
|---|---|---|
| Rotation | none (max-size: -1, max-file: 1) |
20 MB × 5 files |
| Compression | no | yes, by default |
| Cap per container | unbounded | ~100 MB |
docker logs works normally with local. Docker's docs do not state a
preference between the two drivers, so this is a better-defaults choice rather
than an official recommendation — the equally valid alternative is keeping
json-file and adding "log-opts": {"max-size": "10m", "max-file": "3"}, which
preserves compatibility if anything ever parses the raw JSON log files. With
local, don't: Docker warns those files "are designed to be exclusively
accessed by the Docker daemon".
Requires a Docker restart, and applies only to newly created containers — so the cheapest moment to change it is right after a reset, when none exist.
Tarballs, manifest, and backup.sh / restore.sh live in
~/docker-volume-backups/. Restore verifies sha256 against MANIFEST.txt and
refuses to overwrite a volume that a running container has mounted.
~/docker-volume-backups/<date>/restore.sh --verify # integrity check only
~/docker-volume-backups/<date>/restore.sh # restore missing volumes
~/docker-volume-backups/<date>/restore.sh --force # REPLACE existing onesWorth backing up: *-claude-config, *-fish-data, *-fish-history, *pgdata*,
*sql-data*, *storage-data*.
Regenerable, don't bother: vscode, *node-modules*, *playwright-browsers*,
images, container layers.
-
#123 — origin of this page, added as
docs/docker-maintenance-cheatsheet.md - Moved to the wiki because it goes stale when Docker Desktop changes, not when this repo does
- Background and the why: Incident: Docker Disk Exhaustion (2026-09-07)
Last verified: 2026-09-09