Skip to content

Docker Disk Maintenance

Serge Gatezh edited this page Sep 10, 2026 · 2 revisions

Docker maintenance cheatsheet (macOS)

Stock commands only — nothing custom to install. Background and the why live in Incident: Docker Disk Exhaustion (2026-09-07).


"Low disk space" warning — do this first

# 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 df

Safe reclaim, in order of value

docker 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-work

Docker.raw auto-TRIMs after a prune, so the file shrinks on its own. No manual fstrim needed on current Docker Desktop.

The big one: the vscode volume

Usually the single largest item (23.5 GB when last measured). Nothing prunes it.

docker system df -v | grep -i vscode         # check its size first

Closing 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 vscode

Safe 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.

VS Code's own cleanup (Command Palette)

  • Dev Containers: Clean Up Dev Containers…
  • Dev Containers: Clean Up Dev Volumes…

⛔ Do NOT run these

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.


Reclaimed space didn't show up in df?

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 cause

They 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 4

Deleting local snapshots does not affect Time Machine backups on an external or network disk.


Docker is hung — every command just sits there

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'|' -k2

Crash-killed containers all share the daemon-boot timestamp.


Settings worth having

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.


Volume backups

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 ones

Worth 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.


Sources

Last verified: 2026-09-09