The balloon is a placebo: Apple's VM framework never gives freed memory back

AprilNEA1 pts0 comments

The Balloon Is a Placebo: No Container Runtime Can Give Your Mac's RAM Back | ArcBox<br>GitHubDiscord<br>svg]:px-2.5 h-9 rounded-full bg-accent px-5 text-primary-foreground hover:bg-accent/90">Download

[Virtualization]The Balloon Is a Placebo: No Container Runtime Can Give Your Mac's RAM Back<br>2026-08-1013 min read<br>AprilNEA<br>GitHub

If you run containers on a Mac, you know the shape of this complaint: Docker (or OrbStack, or Colima, or container) slowly eats your RAM, and quitting every container doesn't give it back. The most-commented memory issue on docker/for-mac has been open since 2022. Lima has one. Colima has one. Apple's own container tool shipped with the same behavior, documented as a known limitation.

The usual instinct is to blame the runtime. We build one of these runtimes (ArcBox), so we had a professional interest in finding the actual culprit. We spent late July instrumenting both macOS hypervisor stacks — Apple's Virtualization.framework and our own VMM on the lower-level Hypervisor.framework — and calibrating every memory-release primitive Darwin offers.

This post builds the story from the ground up: what is actually running underneath your containers, who owns the memory it eats, what we measured, and why every serious runtime is quietly leaving the stack Apple recommends. Every claim is measurable, and everything is reproducible on your own Mac with two commands and no root — the companion repo is at the end.

What is actually running when you docker run on a Mac

Containers are not tiny virtual machines. On Linux, a container is just a walled-off process — the "walls" (namespaces, cgroups) are Linux kernel features. macOS has no Linux kernel, so every container runtime for the Mac ships one: it boots a real, mostly invisible Linux virtual machine and runs all of your containers inside it.

you: docker run …

container runtimeDocker Desktop · OrbStack · Colima · ArcBox

a hidden Linux VMboots once, runs as long as the runtime does

nginx

postgres

your app

So "Docker is eating my RAM" is really "a Linux VM is eating my RAM." That reframes the question. When Linux inside the VM frees memory — your build finishes, your containers exit — does the Mac outside the VM ever get that memory back?

To answer it, you have to know who owns the VM's memory. And on macOS, that depends entirely on which of two APIs the runtime was built on.

Who owns a VM's memory on macOS

Apple ships two ways to run a virtual machine. Virtualization.framework (VZ) is the high-level, recommended one — hand it a kernel, get a VM object, done. It is what almost every runtime uses. Hypervisor.framework (HVF) is the low-level one: you bring your own virtual devices, your own boot protocol, your own everything. Almost nobody volunteers for that. The difference that matters for memory is where the guest's RAM lives:

Virtualization.frameworkwhat nearly every runtime shipsYour container runtimeDocker Desktop · OrbStack · Colima · ArcBoxXPCApple's VM helper processcom.apple.Virtualization.VirtualMachineGuest RAM lives hereoutside your process — unreachablethe guest kernel, your containers,and every page they ever touchmadvise() only works on memory you own.Every runtime on this stack is locked out.Hypervisor.frameworkone level down — bring your own VMMYour VMM processArcBox HV backend · libkrun · Docker VMMGuest RAM lives heremmap()'d by you, in your address spacehv_vm_map()CPU stage-2 translationthe guest runs directly on your pagesthe pages stay yours to madvise()Reclaim is an engineering task —not a process boundary.<br>Keep this picture in mind — everything below is downstream of which box the guest's RAM sits in. On VZ, guest RAM lives in Apple's XPC helper, which is also where Activity Monitor bills it. The APIs that give memory back to macOS — the madvise family — operate on your own address space. You cannot advise pages you do not map.

With the map established, here is the uncomfortable conclusion up front:

On Virtualization.framework, guest memory is a one-way ratchet. The host cost of a Linux VM is the high-water mark of pages the guest has ever touched, it never goes back down while the VM runs, and no application-level code — not Docker's, not ours — can fix it, because the memory belongs to a process Apple owns. The balloon device that is supposed to fix it measurably does nothing host-side. It is a placebo.

Everything below is measurable, and we cite either our own numbers or primary sources for each claim.

The ratchet, measured

Setup: macOS 26.4, Apple Silicon, a 16 GiB Linux VM on Virtualization.framework (VZ). We sampled the XPC helper's phys_footprint — the same ledger the macOS memory-pressure machinery uses.

StageHost phys_footprint of the VM processFresh boot, idle 5+ minutes718 MB Guest allocates 3 GB (tmpfs)3,717 MB Guest frees all of it3,717 MB — unchanged, indefinitely

3,717 MB718 MBwhat your Macnever gets backmeasuredwhat you'd expectfresh boot, idleguest allocates 3 GBguest frees all of...

memory runtime apple container linux guest

Related Articles