Apple Container Memory Limitations: Understanding Ballooning and Static Allocation Constraints

Apple Container cannot dynamically return freed memory to macOS due to partial memory ballooning support in the Virtualization framework, requiring manual container restarts to reclaim host RAM.

Apple Container (the apple/container repository) implements containerized workloads using lightweight Linux VMs managed by the macOS Virtualization framework. While this architecture provides excellent isolation and security, the current implementation imposes specific Apple Container memory limitations that developers must understand to avoid host-level memory pressure when running multiple workloads.

How Apple Container Manages Memory

Apple Container runs each container inside a dedicated Linux VM rather than directly on the host kernel. This design choice, implemented in the Virtualization framework, creates specific constraints regarding how memory is allocated and reclaimed between the guest VM and macOS.

Partial Memory Ballooning Explained

The macOS Virtualization framework implements only partial support for memory ballooning, a mechanism that typically allows a VM to return unused RAM to the host. According to the technical documentation in docs/technical-overview.md, while the Linux guest can shrink its internal memory usage and mark pages as freed, these pages are never returned to the macOS host.

This means that when a container process allocates and then frees memory, the VM retains that physical RAM from the host's perspective. As documented in the repository: "If you run many memory-intensive containers, you may need to occasionally restart them to reduce memory utilization." The ballooning driver exists but only operates in one direction—memory can be allocated to the VM but not reclaimed by macOS without destroying the VM.

Static Per-VM Memory Limits

Container memory limits specified via the --memory flag affect only the virtual machine backing the container, not traditional cgroup limits. In Sources/ContainerPersistence/ContainerSystemConfig.swift, the default per-container allocation is set to 1 GiB (or half of host memory, whichever is smaller), while the builder VM defaults to 2 GiB.

These limits are enforced statically at VM launch time. Once the VM starts, it reserves the full memory allocation from the host, regardless of actual workload consumption. The MemorySize type in Sources/ContainerPersistence/MemorySize.swift handles parsing of these limits, but the underlying Virtualization framework prevents dynamic resizing after initialization.

Practical Impact and Workarounds

The combination of partial ballooning and static allocation creates a scenario where host memory usage only increases as containers run, even if the workloads inside them are idle or have freed memory.

Monitoring Memory Usage

To identify memory pressure caused by these limitations, use the container stats command to inspect resource utilization:


# Run a container with an explicit 8 GiB memory limit

container run --rm --memory 8g alpine:3.22 sh -c "sleep 60"

While the container runs, inspect its resource usage:


# View memory usage vs. memory limit

container stats <container-id>

Even after the process inside the container exits, macOS Activity Monitor will continue showing the VM holding approximately 8 GiB of host RAM until the container is stopped.

Restarting Containers to Reclaim RAM

Since the Virtualization framework cannot reclaim freed pages, you must manually restart containers to return memory to the host:


# Stop and restart a long-running, memory-intensive container

container stop <container-id>
container start <container-id>

This workflow destroys the underlying VM and creates a fresh instance, effectively flushing the accumulated unused memory back to macOS. For production deployments running many memory-intensive containers, scheduling periodic restarts or implementing memory-aware orchestration becomes necessary to prevent host-level out-of-memory conditions.

Summary

  • Partial ballooning limitation: The macOS Virtualization framework only implements partial memory ballooning, preventing freed pages inside the Linux guest from returning to the host.
  • Static allocation: Memory limits are set at VM launch time and remain reserved from the host regardless of actual usage, with defaults defined in ContainerSystemConfig.swift.
  • Manual reclamation required: You must restart containers to free host RAM, as documented in docs/technical-overview.md.
  • Default values: Standard containers default to 1 GiB (or half host memory), while builder VMs default to 2 GiB unless overridden with the --memory flag.

Frequently Asked Questions

Why doesn't Apple Container return freed memory to macOS?

The limitation stems from the macOS Virtualization framework's implementation of memory ballooning. While the Linux guest VM can mark pages as freed internally, the framework does not support returning those pages to the host macOS kernel. This is explicitly documented in docs/technical-overview.md as a partial implementation of the ballooning mechanism.

What is the default memory allocation per container?

According to Sources/ContainerPersistence/ContainerSystemConfig.swift, the default per-container memory allocation is 1 GiB, calculated as half of the host's physical memory if smaller. Builder VMs default to 2 GiB. These values are parsed using the MemorySize type defined in Sources/ContainerPersistence/MemorySize.swift and enforced at VM creation time.

How can I check if a container is consuming excessive host memory?

Use container stats <container-id> to view the memory limit versus current usage. However, note that host-level memory consumption will remain at the allocated limit even if the stats show low usage inside the container. Check macOS Activity Monitor to see the actual reserved memory held by the Virtualization framework process, which will match the --memory limit set at startup.

Will future updates improve memory ballooning support?

The current implementation in the apple/container repository specifically documents the partial ballooning support as a limitation of the underlying macOS Virtualization framework. Any improvements would require changes to the framework itself rather than the Container codebase. Until then, the recommended mitigation is periodic container restarts to force VM recreation and memory reclamation.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →