Limitations of Memory Ballooning in Container VMs: Why macOS VMs Can't Shrink Memory
Memory ballooning in the apple/container project only supports upward memory growth; VMs cannot return freed pages to macOS, requiring full restarts to reclaim memory.
Memory ballooning in container VMs allows dynamic memory allocation, but the apple/container implementation faces significant constraints. While the macOS Virtualization framework can grow a VM's memory footprint on demand, it lacks the ability to shrink memory back to the host. These limitations of memory ballooning in container VMs create operational challenges for long-running workloads and multi-container environments.
How Memory Ballooning Works in the macOS Virtualization Framework
The macOS Virtualization framework provides memory ballooning capabilities that allow virtual machines to dynamically adjust their memory usage. However, as implemented in the container project, this feature represents only a partial implementation of the full specification. The system can provision additional memory to a VM when container workloads demand it, but the reverse operation—returning unused memory to the host—is not supported.
The Five Critical Limitations of Memory Ballooning in Container VMs
Partial Ballooning Support
The framework can grow the VM's memory when container workloads demand it, but it cannot shrink the VM by returning freed pages to macOS. According to docs/technical-overview.md (lines 57-60), once the VM allocates a page, that memory stays reserved for the VM even after container processes release it internally. This asymmetry means that memory allocation is strictly monotonic during a VM's lifetime.
No Host-Side Reclamation
Memory pages that Linux inside the container frees are never handed back to the host operating system. When processes inside the container release memory, the Linux kernel marks those pages as free, but the macOS Virtualization framework does not reclaim them. This means that if many containers run memory-intensive workloads, the host's overall memory usage remains elevated until the containers or VM are restarted.
Restart Required for Memory Reduction
Because the VM cannot shrink on its own, the only way to reduce its resident memory footprint is to stop and recreate the container or restart the entire VM. This creates operational overhead for long-running services that need to scale down memory usage after completing peak workloads. The memory configuration applied in Sources/Services/MachineAPIService/Server/MachinesService.swift via bootConfig.memory persists until the VM lifecycle ends.
macOS Version Constraints
Full ballooning capabilities only work on macOS 26 (the version shipping with the latest Apple Silicon). On macOS 15, the framework lacks the newer APIs required for reliable ballooning operation, making the feature unavailable or unstable on older releases. As noted in docs/technical-overview.md (line 61), users on earlier macOS versions cannot utilize the limited ballooning features at all.
Impact on Multi-Container Environments
When several containers share a single VM, the inability to release memory means aggregate memory footprints can quickly exceed host capacity. Administrators must manually stop or restart containers to free RAM, complicating resource management in shared environments. The validation logic in Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift enforces minimum memory limits, but cannot prevent cumulative memory bloat across container lifecycles.
Working With Memory Limits in Practice
Despite these limitations, you can observe and manage memory allocation using the container CLI. The following examples demonstrate how memory limits are applied and why the ballooning behavior requires specific operational workarounds.
Launch a container with an explicit memory limit. The VM allocates up to 8GiB but only uses what the process needs initially:
container run -d --name myapp --memory 8g myimage:latest
Check current memory usage while the container runs:
container stats myapp
The output shows the current consumption against the limit:
MEMORY USAGE / LIMIT 1.3GiB / 8GiB
Stop the container and observe that VM memory does not shrink:
container stop myapp
Even after stopping the container, Activity Monitor still shows high resident memory for the VM because ballooned pages were never returned to macOS.
Force memory reclamation by restarting the VM:
container machine stop
container machine start
After the restart, the VM's RAM footprint returns to its default baseline, effectively reclaiming the memory that was previously trapped.
Summary
- Memory ballooning is unidirectional: The apple/container implementation only supports memory growth, not shrinkage.
- Freed pages remain trapped: Memory released by Linux inside the container stays reserved by the VM and cannot return to macOS.
- VM restarts are mandatory: The only method to reduce resident memory footprint is stopping and restarting the VM or container.
- macOS 26 is required: Full support requires the latest Apple Silicon release; macOS 15 lacks necessary APIs.
- Multi-container environments suffer: Aggregate memory pressure increases without automatic reclamation, forcing manual intervention.
Frequently Asked Questions
Can a container VM release memory back to macOS?
No. According to the apple/container source code in docs/technical-overview.md, the macOS Virtualization framework can grow VM memory on demand, but it cannot shrink the VM or return freed pages to the host. Once allocated, memory pages remain reserved for the VM until it is completely restarted.
Why does my container's memory usage stay high after stopping processes?
Because the implementation lacks host-side reclamation. When Linux inside the container frees memory via standard process termination, those pages stay within the VM's allocation. While container stats may show reduced usage inside the guest, the underlying macOS VM retains the physical memory pages indefinitely.
Which macOS versions support memory ballooning?
Reliable memory ballooning requires macOS 26 (the version shipping with the latest Apple Silicon hardware). On macOS 15, the framework lacks the necessary Virtualization APIs for ballooning support, preventing the feature from working even in its limited capacity as documented in the technical overview.
How do I reduce memory usage for long-running containers?
You must restart the VM using container machine stop followed by container machine start, or recreate the container entirely. Consider implementing cron-based restart strategies for fluctuating workloads, or pre-size containers conservatively using the --memory flag to prevent excessive host memory consumption.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →