No space left on device is an honest error from the kernel: a write was refused because the filesystem had nowhere to put it. The disk is full. What makes it confusing on a Mac is that deleting files often does not fix it, and that the space is frequently held by something you have no memory of creating.
The examples below come from macOS 26.5.2, using a small disk image deliberately filled up so the failure is real rather than described.
What the error looks like when it is real
Writing a file larger than the space available produces the error from whichever program was doing the writing:
$ dd if=/dev/zero of=/Volumes/FullTest/big.bin bs=1m count=100
dd: /Volumes/FullTest/big.bin: No space left on device
11+0 records in
10+0 records out
Note that dd wrote 10 of the 100 MB it was asked for and then stopped. A partial file is the normal outcome, not a rollback, which is why a failed copy can leave something that looks complete until you check its size.
cp can fail in a way that is easy to misread:
$ cp /etc/hosts /Volumes/FullTest/hosts.txt
cp: /etc/hosts: could not copy extended attributes to /Volumes/FullTest/hosts.txt: No space left on device
The file contents fit; the extended attributes did not. You get a file at the destination that is missing its metadata, and an exit status of 1 — worth knowing if a backup script reports the copy as done and the run as failed.
Snapshots and purgeable space
This produces the dramatic version of the problem — hundreds of gigabytes of files deleted, and not a byte returned. Time Machine keeps local snapshots on the internal disk, and a snapshot pins every block the deleted files were using.
$ tmutil listlocalsnapshots /
Snapshots for disk /:
A heading with nothing under it means no snapshots are held, as above. When there are, each line is a timestamped com.apple.TimeMachine entry, and the space they hold is what macOS calls purgeable: reported as free in Finder, still occupied on disk until the system decides to reclaim it.
macOS does purge them automatically when a volume comes under pressure, which is why the advice to just keep working sometimes does resolve it. When you need the space now, tmutil thinlocalsnapshots / <bytes> 4 asks for a specific amount back at the most aggressive urgency level, and tmutil deletelocalsnapshots <date> removes one outright. The trade is real: those snapshots are what a Time Machine restore uses when the backup drive is not attached, so removing them costs you the ability to roll back to that point in time.
If you run Docker, look there first
Docker accumulates quietly and at a scale nothing else on a Mac matches. Before deleting anything, get the breakdown:
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 91 26 45.36GB 20.37GB (44%)
Containers 26 12 324.8MB 272.1MB (83%)
Local Volumes 58 18 5.511GB 2.435GB (44%)
Build Cache 653 0 53.15GB 46.98GB
That is a real development machine, and it is worth reading closely. Build cache is the largest single item at 53 GB, and its ACTIVE column is 0 — nothing running depends on it. Local volumes are only 5.5 GB, but that is where databases and application state live. The two are not remotely equivalent in risk, which is exactly what the popular one-line cleanup commands ignore.
Work outward from the safest thing:
docker builder prune --filter until=168h— drops build cache older than a week. Almost always the biggest win and the smallest loss: the worst case is that one later build runs slower. On current Docker this dispatches todocker buildx prune, which also accepts--max-used-spaceand--reserved-spaceif you would rather cap the cache than clear it.docker image prune— removes only dangling images, the untagged layers left behind by rebuilds. Adding-achanges it into “remove every image not used by a running container”, which will happily delete images you still need and cannot re-pull offline.docker ps -athen remove specific stopped containers by name. Reading the list first takes seconds and tells you whether anything there matters.
The command to understand before running is docker system prune -a --volumes. It is the first result for almost every “Docker using too much disk” search, and --volumes is the dangerous half: named volumes hold database contents, uploaded files and anything else a container was meant to keep. They are not backed up anywhere, and they are not in the Trash afterwards. A database you have been running locally for a year disappears in the time that command takes to return.
If you have reached for it before and nothing broke, that means the volumes were empty or disposable — not that the flag is safe. Prune the build cache first, re-run docker system df, and in most cases the problem is already solved without touching a single volume.
Finding what is actually using the disk
For everything else, du is the tool, with two flags that matter:
$ du -shx -d1 ~/ | sort -h
-d1 stops it recursing forever and gives one line per top-level folder; -x keeps it from wandering onto other mounted volumes, which on a Mac with an external drive attached otherwise makes the totals meaningless. Piping to sort -h puts the biggest at the bottom, next to the prompt.
Expect noise on the way: du prints Permission denied for directories your account cannot enter, and it still prints a total afterwards — a total that is short by whatever it could not read. Running it with sudo gives the accurate figure. Sending the errors to /dev/null hides the warning that the number is incomplete, so it is worth reading them at least once. The larger cleanup targets, and which of them actually return space, are covered in the storage cleanup guide.
A full disk can still delete
One worry that turns out to be unfounded. On the test volume at 91% with writes already failing, rm returned 0 and the space came back immediately:
$ df -h /Volumes/FullTest2
/dev/disk20s1 12Mi 10Mi 1.6Mi 87% ...
$ rm /Volumes/FullTest2/big.bin ; echo $?
0
$ df -h /Volumes/FullTest2
/dev/disk20s1 12Mi 20Ki 12Mi 1% ...
Which means a disk that stays full after you delete things is telling you something specific — the blocks are pinned by a snapshot, or they are in the Trash, or they belong to a file some process still has open. It is never that the volume was too full to accept the deletion.
That last one is worth a check before you go looking further: deleting from Finder moves files to the Trash on the same volume, so nothing is freed until it is emptied.