Where a VM's disk space goes
The disk-space study of 2026-10-06 to 2026-10-09: what a machine's ~20 GB is made of, what each way of saving it was measured to save, and why one template per firmware with new-identity clones became the default. The mechanisms have notes of their own, linked below; this note keeps the composition, the comparison and the end-to-end numbers.
Unless stated otherwise the guests are iPhone17,3 (or iPhone99,11) on iOS 27.0
(24A435) with cloudOS 26.4 (23E5207q), standard preset, a 64 GB sparse
Disk.img, on an APFS host volume. Block sharing was measured with
fcntl(F_LOG2PHYS_EXT) on both files ("physically shared"), not with du.
Summary
- A restored machine that has booted once allocates about 20.5 GB. About
8.6 GB of it is the IPSW's root file system, 7.2 GB the OS and App
cryptexes
cfw installcopies onto the System volume, and 2–5 GB the Data and User volumes' first-boot state. Building one also needs the ~11 GB restore tree and 13.5 GB of IPSWs. - Space the guest frees comes back to the host by itself, within about 15 s (guest TRIM). There is nothing worth compacting offline (guest TRIM).
- The saving that matters is sharing. A template per firmware, and every
machine a
clonefilecopy of it with a new ECID, costs each machine about 0.35 GB after its first boots instead of 20.5 GB (templates, identity and clone). - Everything else is small next to it and counts once per template, not per
machine: the offline file trim 1.2 GB, the orig-fs snapshot itself about
21 MB, deleting the IPSWs 13.5 GB per build.
vm rebasegets back about 8.7 GB per pair of machines restored separately (disk rebase). - Two further approaches were studied and not built: making the restore's disk layout deterministic (deterministic layout) and a content-addressed block store behind Virtualization's NBD client (block store over NBD).
What a machine holds
The machine folder
Inside Disk.img
Attached read-only (diskutil image attach --readOnly --noMount), a booted
machine's container showed:
The container's "In Use" (21.0 GB) matched the image's allocated size: the
blocks cfw install freed on the host had been punched out of the file, so a
fresh machine carries no dead space.
The System volume is two layers:
- The sealed root file system restore wrote (ASR, then
apfs_invert). CFW renames its snapshot toorig-fs.disabled.rn-<hash>, and the patched kernel boots the live volume. The snapshot still references the original 8.59 GB; of the CFW-modified files, only 35 MB of old versions are held by nothing else (21.5 MB measured when the snapshot was deleted, see snapshot deletion). - The OS and App cryptexes (7.2 GB on the live volume), which
cfw installcopies from the restore tree intoSystem/Cryptexes/{OS,App}and links in (System/Library/Caches/com.apple.dyldpoints intoSystem/Cryptexes/OS/…). vphone boots without the cryptex graft (cryptex1 sniff: ignition failed: 8on every boot), so the System volume copy is the only one dyld finds. The snapshot does not hold these blocks.
The live volume by kind, deduplicated by inode, st_blocks (2026-10-07):
The dyld cache is 45% of the System volume and can only be kept or replaced
whole. Most localizations live in .loctable files, all languages in one
file, so trimming languages from .lproj folders would save about 0.3 GB at
most.
Data and User
The host cannot mount these volumes (their keys are wrapped by the guest's
SEP), so their content was only classified by block. After the first boot
they hold 2–5 GB: the restore's fixup_var, the first boot's data migration,
caches, logs and the expanded removable system apps. This is the part each
clone writes for itself; see the per-clone numbers below.
What each approach saves
Projected host bytes for N machines per build and K builds (IPSW caches not counted; template 18.6 GB, 0.35 GB per clone after first boots, 20.5 GB per separately restored machine; the CAS column is 4 KiB blocks with LZFSE):
vm rebase does not change the template column: templates of different
builds share no block at the same offset (0.00 GB between an iPhone 27.0 and
an iPad 26.6.2 image), and clones already share their template's.
Why templates won
A template is the only approach whose saving grows with the number of
machines and costs nothing on the I/O path: a clone is a clonefile, the disk
stays a raw file the host can attach for cfw install, cfw update-environment, the trim, vm rebase and snapshots, and Virtualization
keeps its write-back cache. Its costs are known and documented: every clone
shares the template's SEP root secret, gigalocker and volume keys
(shared secrets; --no-template makes
a machine with its own), a template must never boot once frozen, and the disk
size is part of the key because no code grows a container after the restore
(a larger size needs another template).
Three places to trim the System volume were considered:
- Before the restore, in the IPSW's root file system image. The repository
has the machinery (the
.lessvariant'sCryptexFilesystemPatcher), but it means decrypting and resealing an 8–9 GB image per IPSW, and whether TSS signs a changedSystemVolume/Ap,SystemVolumeCanonicalMetadatadigest on the JB path was never tested. CFW then rewrites the result anyway. - After
cfw install, on the host, with the System volume mounted: chosen. No guest involved, repeatable, and a raw image is hole-punched when the volume is unmounted (a 1 GB file written and deleted on a test image left 5.6 MB allocated after the unmount, 982 MB before it). - Inside the running guest through vphoned: rejected when templates were still meant never to boot. The setup boot came later, for Setup and app removal, but the host-side trim stayed the simpler choice: it needs no writable System volume in the guest and no destructive RPC.
The catch was the snapshot: deleting root file system files on the host
frees nothing while orig-fs.disabled.rn-* exists, and the host cannot
delete that snapshot (SIP, -69863 even as root). The guest can, so the
setup boot deletes it first (snapshot deletion).
Never trimmed: the dyld cache and its .symbols (the CFW cache patcher's
DyldSharedCacheSymbolResolver reads the local symbols, so a later
cfw update-environment on a clone would fail to find its patch sites), ML
models (Photos analysis, Spotlight, Live Text and the camera pipeline load
them; a missing model makes daemons crash or retry), and /Applications
(system UI and view services). The aggressive tier (other .lproj, Health
assets, some fonts; about 1.7 GB with the standard items) stays reserved because it was never
booted.
Measured end to end
Template builds from scratch, Launchpad 2.9 driving bundle 2.9 builds, IPSWs cached, seconds per step (2026-10-09). The setup boot is mostly the guest's first-boot data migration (Setup Assistant skip).
The third 27.0 build's setup boot: connect 4 s, setup-skip 0 s, settle 63 s,
remove apps 5 s, service profile 0 s, reboot 10 s, verify 1 s, crash reports
1 s, stop 5 s. The first two ran the earlier setup.skip, which retried for
45–98 s while settle then took about 10 s
(setup boot); the setup boot took about as
long either way, because the wait is the migration.
Machines from an existing template:
Sizes:
The template's own private bytes stay at about 0.1 GB, the blocks every clone has rewritten. Booting a template after clones exist raised one clone's private bytes from 0.42 to 0.80 GB, which is why a frozen template never boots (templates).
Measuring it
du, Finder and a folder's allocated size count every clone at its full ~18 GB.dfbefore and after an operation is the honest total.- Per object, compare physical extents (
F_LOG2PHYS_EXT), as Launchpad's Exclusive column does. APFS's private size (ATTR_CMNEXT_PRIVATESIZE) reads 0 for every object after each hourly Time Machine local snapshot, until it writes again (disk use). st_blocksof a running machine's image is not monotonic: guest TRIM pulls it back. Writes by the VM are better read from the Virtualization process'sri_diskio_byteswritten(proc_pid_rusage).- Deleting a template frees only its own blocks while clones exist; deleting a source frees nothing a clone still shares.
Observation to verify: plaintext in Data and User blocks
The block-level survey for the block-store study
(content-addressed store)
found that the image region not mapped to System, Preboot, xART, Hardware or
Update files (Data, User and container metadata), although both volumes show
as FileVault locked on the host, is not uniformly ciphertext. On the iPhone
27.0 image about 2.0 GB of it compressed like plaintext (LZ4 ratio 0.55), and
blocks began with SQLite format 3 (429 blocks), bplist00 (4,006), a PNG
signature (874) and caff (711); 2.6 GB looked random. The iPad 26.6.2 image
showed 3.5 GB compressible and 4.2 GB random.
Read as it stands, someone holding a Disk.img could carve some guest file
contents out of it without the SEP. It has not been checked which files these
are, which protection classes they carry, or whether the content-protection
layer behaves differently on virtio storage than on Apple NAND. Treat it as an
open question, not a finding about iOS.
Open items
- Stop the DFU VM at the iBoot banner. Every restore's post-restore reboot
panics because the System volume has no dyld cache before
cfw install, andvm createand Launchpad wait for that panic; its report then lands in the guest. Stopping at the banner would avoid both for every machine (setup boot, last paragraphs on crash reports). - Cold template archives. Compressing the
Disk.imgof a template no one clones any more (LZFSE, about 30–35% smaller by the block survey) and expanding it back before the next clone would take most of the block store's remaining gain without touching the I/O path. Not built. - ASIF images. Virtualization's XPC service knows
.asif, but whether discard frees blocks in an ASIF image was not tested. Raw sparse files already return freed space,VPhoneAPFSSnapshotreads and patches the image at raw byte offsets, andvm rebasetakes file offsets for disk offsets; ASIF would only help where the host volume cannot hold a sparse file. - Growing a disk. No code resizes a container after the restore, so a
larger disk means another template or
--no-template. - Revisit the block store only if disk space is still the limit with templates, trim and IPSW deletion in place and three or more builds kept hot at once, or if Apple provides a host-mountable user-space block device or an NBD client with write-back and FLUSH (content-addressed store).