Sparse files
The problem
Run ls -lh on Docker’s virtual disk:
-rw-r--r-- 1 al staff 1.0T Docker.raw
One terabyte? On a 460 GB drive? Something doesn’t add up.
Docker.raw is a sparse file. It has a logical size of 1 TiB (the maximum size Docker Desktop allocated for its virtual disk), but most of that space is empty — never written to. The filesystem only allocates blocks for the parts that actually contain data.
Apparent vs. on-disk size
apparent size: 1,099,511,627,776 bytes (1.0 TiB)
on-disk size: 22,388,580,352 bytes (20.9 GiB)
Most disk usage tools — including naive Rust code using metadata.len() — report the apparent size. This is misleading because it counts space that was never allocated.
The actual disk usage comes from the filesystem’s block allocation count:
stat -f "blocks=%b" Docker.raw
# blocks=43727696
# Actual size: 43727696 * 512 = 22,388,580,352 bytes = 20.9 GiB
How oops handles this
oops uses stat.blocks * 512 for every file, matching what du reports by default. This means:
- Sparse files show their real footprint, not their inflated logical size
- Compressed files (on APFS) may show smaller than their logical size
- Regular files show the same size either way (block allocation ≈ logical size)
As a fallback, if blocks is zero (some virtual filesystems), oops falls back to the logical size.
Common sparse files on macOS
| File | Location | Apparent | Typical actual |
|---|---|---|---|
| Docker.raw | ~/Library/Containers/com.docker.docker/Data/vms/0/data/ |
1+ TiB | 5–60 GiB |
| Time Machine snapshots | Various | Varies | Much smaller |
| APFS sparse disk images | .sparseimage files |
Configured max | Actual content |
Why this matters
Without block-level sizing, a tool can report your home directory as 1.3 TiB on a 460 GiB drive. With it, you get the real number — typically 200–300 GiB for a developer workstation. The inflated number makes every other size look proportionally tiny, hiding the directories that are actually consuming your disk.