Storage 5 October 2026 · edition 15 min read

When a developer Mac runs out of disk: APFS volumes, simulators and what is safe to delete

A Mac used for development and builds can go from tens of gigabytes free to almost none overnight. Read the APFS container and the swap volume first, find what is still growing, and delete simulators and build directories only under rules that protect work in progress.

Published 13 November 2026. Hostwatch engineering review; implementation limits are stated below.

A matte dark computer miniature on a stone plinth beside a tall glass vessel packed with dark blocks, with only a thin clear band left at the top and a lens aimed at that band
EDITORIAL IMAGEOriginal AI-generated conceptual illustration, not a product screenshot or production measurement. A development Mac can be almost full before anyone has looked at which part of its storage is growing. Open full size ↗
01 / 10

From tens of gigabytes free to none in one night

One of our development Macs is also a build machine. It compiles iOS apps in Xcode, runs iOS simulators for automated tests, builds Rust and Swift packages and hosts a container engine. Its internal storage holds just under one terabyte. One evening it had several tens of gigabytes free. The next morning it had about 130 MB, and the figure was still falling while we watched it. At that level ordinary work stops. A program that needs to create a temporary file, append to a log or extend a database gets an error, and on this machine even starting a shell command failed.

The obvious reaction is to find the largest directory and remove it. On a machine like this, that reaction destroys work. The largest directory may belong to a simulator that a test run is using at that moment. A file that looks enormous may occupy almost nothing. An old-looking folder may hold the only archive of a build that customers are running. Deleting the wrong thing may also fail to help, because whatever was consuming the space simply continues. A careful response has three parts: read the right numbers, find what is still growing, and delete only what a written rule allows.

This article follows that order and uses the incident as its example. It explains how the Apple File System, APFS, reports space, why swap takes part of the same disk, where space goes on a development Mac and which rules we use for cleaning up. The last section describes what the Hostwatch agent for macOS shows in its storage view and what it leaves to the operator. The Linux side of a full disk, including mounts, inodes and deleted files that are still open, is covered in our earlier article listed in the sources and is not repeated here.

02 / 10

One APFS container, several volumes, one pool of free space

APFS organises a Mac's internal storage as a container that holds several volumes. The word container also describes Docker-style containers, so in this article the APFS container is always called by its full name and the other kind appears only as the container engine. On a current Mac the APFS container holds a System volume, a Data volume and four supporting volumes named Preboot, Recovery, Update and VM. The System volume is sealed, and macOS starts from a read-only snapshot of it mounted at the root path. The Data volume is mounted at /System/Volumes/Data and holds the home directories, the applications and everything a development tool writes.

The volumes have no fixed sizes. Each one grows and shrinks inside the APFS container, and all of them draw on the same free space. Apple's description of APFS states the consequence directly: every volume includes the shared free space when it reports what is available, so adding up the available figures of the volumes does not give the free space of the disk. The output of df shows this. It prints one line per mounted volume, and every line that belongs to the same APFS container has the same size and the same available figure with a different used figure. The line for the root path counts only the System volume.

Someone who reads only that first line sees a small used figure and concludes that the disk is nearly empty. Two numbers describe the whole disk. One is the available figure, which is identical on every line. The other is the summary printed by diskutil apfs list: the capacity of the APFS container, the amount in use by all volumes together and the amount not allocated, followed by the space consumed by each volume. On a development Mac df also lists each mounted simulator runtime as a small separate container that is almost full. On the Xcode version we use, those are read-only disk images sized to their contents, so their percentages are normal and say nothing about the internal disk.

Six dark blocks of different sizes inside one wide glass vessel on a stone plinth, sharing a single narrow empty space that is lit by a light conduit along the rim
EDITORIAL IMAGEOriginal AI-generated conceptual illustration, not a product screenshot or production measurement. The volumes of one APFS container have no walls between them and draw on the same free space. Open full size ↗
03 / 10

The VM volume holds swap, and swap grows when memory is short

The VM volume is where macOS keeps swap. When running programs need more memory than the Mac has, macOS first compresses memory that has not been used recently and then writes part of it to swap files on the VM volume, adding files as demand grows. The command sysctl vm.swapusage prints how much swap exists and how much of it is in use. Because the VM volume draws on the same free space as the Data volume, every gigabyte of swap is a gigabyte that builds and simulators can no longer use, and it appears in no directory under the home folder.

A build machine tends to be short of memory at the same time as it is short of disk. Each booted simulator is a set of running processes, parallel test sessions multiply them, and compilers and the virtual machine of a container engine take their share. During our incident the VM volume held about 19 GB. That was more than the Xcode build directory and the old Rust and Swift build directories put together, and no search of the home directory would have found it.

Swap files belong to the operating system and are not something to delete by hand. The space comes back when memory demand falls, which in practice means shutting down what is not needed, and a restart begins with empty swap. macOS can release swap files after demand has dropped, but we would not plan a recovery around when it does. The dependency also runs the other way. With no free space the swap files cannot grow, so a Mac that is out of disk and short of memory has no slack on either side, and applications start failing for reasons that look unrelated to storage.

04 / 10

Why df answers at once and du takes minutes

Two commands report disk use, and they work differently. df asks the file system for counters it already maintains, so it answers immediately however many files exist. du computes its figure by walking a directory tree and reading the metadata of every file in it. A developer's home directory easily contains millions of files once package manager directories, build intermediates and simulator data are included, and a du over all of it runs for minutes. When commands are already failing for lack of space, that is a long time to wait for one number.

The two commands also measure different things, so their results differ even when both finish. du adds up the blocks of each file it can read. APFS can copy a file as a clone that shares its blocks with the original, and du counts those blocks once for each file. A snapshot keeps blocks that no current path refers to, and du cannot see them at all. Directories protected by the privacy controls of macOS return an error unless the terminal has been granted Full Disk Access. The sum of a directory scan can therefore be larger or smaller than the figure the volume reports.

A workable division is to use df and diskutil for how much is used and how fast that is changing, and du only for where. Read df twice, a minute apart, to learn whether space is still being consumed. Then run du on a few likely directories one level at a time, for example du -x -d 1 -h, where -d limits the depth and -x keeps the walk on one volume. Each run answers one narrow question, and on a development Mac the list of likely directories is short.

05 / 10

Where the space usually goes on a development Mac

The first place to look is the simulators. CoreSimulator, the service behind Xcode's simulators, keeps one directory per simulator under Library/Developer/CoreSimulator/Devices in the home directory, named by the simulator's identifier. Each directory holds the data of that simulated device: installed apps, their files, caches and settings. Automation that creates a new simulator for a test session adds one directory each time, and nothing removes it afterwards unless the automation does. On our Mac this directory held about 72 GB across roughly thirty simulators.

Build output is the second group. Xcode writes build products, intermediate files and indexes to Library/Developer/Xcode/DerivedData. The Swift Package Manager builds into a directory named .build inside each package, and Cargo builds Rust code into a directory named target inside each crate or workspace. In this article these are called build directories, and all of them can be rebuilt from source. They are also what people blame first. Here that would have been wrong: DerivedData was under 4 GB, and the old Rust and Swift build directories together came to about 4 GB.

The third group is caches and images. Homebrew keeps downloaded package archives, npm and pip keep caches of what they have fetched, and on recent Xcode versions each simulator runtime is installed as a disk image of several gigabytes. A container engine keeps one large disk image for its virtual machine, which the next section examines because its size is easy to misread. Xcode also keeps archives of builds prepared for distribution under Library/Developer/Xcode/Archives. Those belong in a separate category from everything above, because they cannot be regenerated.

06 / 10

A one-terabyte file that uses a gigabyte and a half

A container engine on a Mac runs Linux containers inside a virtual machine, and it typically keeps the disk of that machine as a single image file on the Data volume. On our Mac a file listing showed that image with a size of one terabyte, on a disk of just under one terabyte that had run out of space. It is tempting to stop the investigation there. The file was in fact using about 1.5 GB.

The image is a sparse file. APFS allocates blocks for a file only when those blocks are written, so a file can have a length far greater than the storage it occupies. The length is called the apparent size, and for a virtual disk it is the maximum the disk may grow to. The allocated size is what the file actually takes from the APFS container. Different commands read the two: ls -l prints the apparent size, du prints the allocated size, and du with the option -A prints the apparent size again. Before blaming any single large file, compare the two figures.

Deleting that image would have returned about 1.5 GB and destroyed every image, container and volume stored inside the engine. The opposite mistake is also possible. The allocated size grows as the virtual machine writes, and whether it shrinks again after data is removed inside the engine depends on the engine, so an image that is small today should be measured again the next time. A tool that adds up apparent sizes will, for the same reason, report more data than the disk can hold.

A very tall glass column on a stone plinth, empty except for a shallow layer of dark granules at the bottom, next to a short solid dark block, with a lens aimed at the shallow layer
EDITORIAL IMAGEOriginal AI-generated conceptual illustration, not a product screenshot or production measurement. A sparse disk image has a large apparent size and occupies only the blocks that were actually written. Open full size ↗
07 / 10

Rules for simulators: delete through simctl, and never a booted one

The command line tool for CoreSimulator is simctl, run as xcrun simctl. The command xcrun simctl list devices prints every simulator with its identifier and its state, which is normally either Booted or Shutdown. With the option for JSON output, the Xcode version we use also reports the size of each simulator's data directory and the time the simulator was last used. That is enough to decide about each simulator without measuring directories by hand.

The first rule is to delete a simulator with xcrun simctl delete followed by its identifier, and never by removing its directory. CoreSimulator keeps its own record of the simulators it manages next to their directories. Removing a directory behind its back can leave that record, and any processes still running for the simulator, out of step with what is on disk. The simctl command asks the service to remove the simulator and its record together. It also accepts the word unavailable in place of an identifier, which deletes the simulators that the installed Xcode no longer supports.

The second rule decides which simulators qualify. A simulator qualifies only if it is shut down and has not been used for several days. A booted simulator is never deleted, because its state means something is using it, and on a build machine that is usually a test session in progress. On our Mac several of the roughly thirty simulators were booted and in use by parallel automated test sessions. Eleven were shut down and had been idle for more than two days. Deleting those eleven removed about 27 GB, and the booted ones were left untouched. Simulator runtimes need separate care: the help text of simctl says that deleting a runtime shuts down the booted simulators that use it, so it is not a step to take while tests are running.

08 / 10

Build directories, archives and anything that is the only copy

Build directories can be regenerated, which makes them safe to delete in principle and costly to delete carelessly. A build that is running fails when its directory disappears. The next build of that project starts from nothing, and on a large project that means a long wait and a heavy load on a machine that is already in trouble. Our rule is to remove a build directory only when no file in it has changed for several days. The find command can check this. Asked for regular files modified within the last three days, with the options -type f and -mtime -3, it should print nothing for a directory that is about to be removed.

The rule is applied to DerivedData one project subdirectory at a time, and to each .build and target directory separately. It removes the build directory and leaves the project around it alone. A project folder that looks abandoned can contain changes that were never committed, a local database or notes that exist nowhere else. Deleting only the directory that the build tool created avoids having to judge whether the rest matters.

Some things are left alone whatever their size or age. Xcode archives of released builds contain the debug symbol files that turn the addresses in a crash report back into function names, and a later rebuild produces a different binary whose symbols do not match the one users are running. The data inside the disk image of a container engine, signing keys and anything else that exists as the only copy belong to the same category. Package caches sit at the other end. They can be downloaded again, so trimming old files from them carries little risk, and the cost is the time and network traffic of fetching those files when they are next needed.

09 / 10

Free space comes back over minutes and is then used again

After the eleven simulators were deleted, the free figure did not jump. It read about 5 GB and rose over several minutes to more than 30 GB, as APFS released the blocks. We did not trace the file system during the incident, so we can report what we saw and what Apple documents. Apple's file system reference describes a reaper, a mechanism that allows large objects to be deleted over a period spanning multiple transactions. A snapshot that still refers to deleted files is a different case: it keeps their blocks allocated until the snapshot itself is removed.

The practical consequence is to wait before deciding that a deletion did not work. Read df every half minute for a few minutes after a large delete. Someone who looks once, sees almost no change and deletes more in a hurry ends up removing things the rules would have kept. If the figure stays flat for longer, check for local snapshots. Time Machine creates them, and the command tmutil listlocalsnapshots, given the root path, lists them.

Half an hour after the cleanup, about 8 GB of the recovered space had been consumed again by new simulators. The test automation was still running and kept creating them, so a one-off cleanup bought hours. The durable fix is a rule for the automation that creates simulators. A session deletes the simulator it created when it ends, including when it fails. The number of simulators that may exist at once has a limit. A scheduled sweep applies the same test as the manual cleanup, shut down and idle for days. The automation should also check free space before it starts a new session, which is the idea behind the build reserve described in our article on small Linux servers: confirm that the room exists before starting work that needs it.

A row of small dark handheld device miniatures on a long stone plinth, some joined by lit conduits to a server miniature, others unlit and moved to a lower tray, while a chute lowers two new ones into the freed space
EDITORIAL IMAGEOriginal AI-generated conceptual illustration, not a product screenshot or production measurement. Idle simulators can be removed while those in use stay connected, and automation that keeps creating new ones fills the freed space again. Open full size ↗
10 / 10

What Hostwatch shows for a Mac, and what it leaves to you

Hostwatch has an agent for macOS alongside the Linux one, and the web console presents the disk of a Mac in the order used above. The Disk details open with the APFS container: the space occupied across all volumes and the space available. Below that, the volumes of the APFS container are listed by name with the space each one uses, largest first, so the VM volume appears next to the Data volume. The difference between the occupied figure and the sum of the volumes is shown as an accounting gap, and the view notes that volume sizes include data a directory scan may not see, such as snapshots. Only the APFS container that holds the startup volume is listed.

Directory sizes come from a separate read-only scan that runs in the background and is limited to the Data volume. A path outside that volume is refused. The agent measures the top-level directories one at a time and leaves the user directories, which are usually the largest, for last, so each size appears as soon as it is ready. A directory that has not been measured is marked as still being measured and is never shown as zero. Opening a directory starts a scan of the next level. Each directory has a time limit of thirty seconds and a whole scan has eight minutes. A directory that runs past its limit, or that the agent cannot read completely, gets no size, and the scan is marked partial while the sizes already measured stay visible. A large home directory can exceed the limit at the top level, so expect to go down a level or two, and expect directory totals to differ from the volume because of shared blocks and snapshots. A finished scan is kept for up to an hour unless you request a rescan.

Cleanup on a Mac is narrow by design. The storage cleanup guide describes the general flow of preview and approved targets. On a Mac the approved targets are three package caches at fixed locations in the user's home: Homebrew downloads older than seven days, npm cache content older than thirty days and pip wheel files older than thirty days. Xcode, simulators, project builds, databases and backups are excluded. A request cannot name another path, and a scan that did not finish cannot become a deletion plan. Scheduled cleanup is off by default, cleaning requires an owner or admin of the workspace, and access to a Mac through the Hostwatch MCP server is read-only. The preview counts logical file sizes, so the space actually returned can be smaller. The rules above for simulators and build directories are therefore still carried out by the operator or by the automation that owns those files. For them, Hostwatch shows where the space is and removes nothing.

THE TAKEAWAY

Read the APFS container and the swap volume before any single directory, and find what is still growing before deleting. Remove simulators only through simctl and only when they are shut down and idle for days, remove build directories only when nothing in them has changed for days, and keep archives and only copies. Then give the automation that creates simulators a rule that removes them, because a one-off cleanup lasts hours.

Further reading

UP NEXT / Ingress and TLSPublishing a new site behind Nginx in three stages: HTTP, certificate, HTTPS