# Limitations of Memory Ballooning in Apple Container: Why VMs Cannot Return Memory to the Host

> Discover why Apple Container's memory ballooning in the macOS Virtualization framework only allows VMs to allocate memory, not return it to the host. Learn the limitations.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: deep-dive
- Published: 2026-07-02

---

**Apple Container relies on the macOS Virtualization framework, which only supports memory ballooning for allocation, not reclamation—meaning VMs can grow their memory footprint but never return unused pages to the host operating system.**

The `container` project by Apple leverages macOS's native Virtualization framework to run Linux workloads in lightweight VMs. While this architecture provides security and isolation, it inherits significant **limitations of memory ballooning** from the underlying framework that affect how memory is managed between the guest and host systems.

## How Memory Ballooning Works in Apple Container

When you launch a container using the `container run` command, the tool creates a lightweight Linux VM through the macOS Virtualization framework. This framework implements **partial memory ballooning** support, allowing the VM to request additional memory from the host when workloads demand it. However, as documented in [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md) [¶55-60], the framework does not implement the reverse operation: returning freed memory back to macOS.

The `container` binary accepts memory limits via the `--memory` flag (parsed in [`Sources/ContainerPersistence/MemorySize.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MemorySize.swift)), but this only establishes a maximum allocation ceiling. Once the VM claims memory from the host, that memory remains reserved regardless of whether the guest Linux kernel subsequently frees it internally.

## The Three Critical Limitations

### Allocation Without Reclamation

The primary limitation is that **only allocation is supported, not reclamation**. When a containerized workload requests memory, the Virtualization framework grows the VM's footprint accordingly. However, when processes inside the container terminate and the guest OS frees those pages, the framework does not shrink the VM's allocation or return that memory to macOS.

Consequently, a container that temporarily consumes 16 GiB of RAM will continue to hold that amount of host memory indefinitely, even if the workload drops to near-zero usage. The [`MemorySize.swift`](https://github.com/apple/container/blob/main/MemorySize.swift) file handles the parsing of user-specified limits, but the underlying framework provides no API for the guest to signal that pages are no longer needed.

### The Restart Workaround

Because memory cannot be reclaimed dynamically, **manual restarts become necessary** for memory-intensive workloads. When multiple containers consume large amounts of RAM concurrently, the host's memory pressure remains high even when container activity subsides.

The only reliable method to force memory release is to destroy and recreate the VM:

```bash

# Start a memory-intensive container

container run --rm --memory 16g heavy-app

# Inside the container, terminate memory-intensive processes

# Host Activity Monitor still shows ~16 GiB allocated to the VM

# Force memory release by restarting

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

```

### Framework-Level Constraints

This behavior is **not a bug in the `container` tool** but rather a limitation inherent to the macOS Virtualization framework itself. The `container` code cannot override this behavior because the framework does not expose APIs for guest-to-host memory deallocation. Until Apple extends the Virtualization framework to support full bidirectional ballooning, the `container` project cannot implement automatic memory reclamation.

## Code Examples and File References

The memory limit specified via CLI is processed through the persistence layer before being passed to the Virtualization framework:

```swift
// Sources/ContainerPersistence/MemorySize.swift
import ContainerPersistence

do {
    let mem = try MemorySize("16g")
    print("Requested memory: \(mem)")               // → "16gb"
    print("Bytes: \(mem.toUInt64(unit: .bytes))")   // → 17179869184
} catch {
    print("Invalid memory spec")
}

```

Even when you explicitly set memory constraints, the VM respects the maximum while ignoring decrementation requests:

```bash

# The --memory flag caps maximum allocation only

container run --rm --memory 8g my-image

# The VM may grow up to 8 GiB but will never shrink below peak usage

```

## Summary

- Memory ballooning in Apple Container only supports upward allocation; freed pages are never reclaimed by the host.
- The limitation originates in the macOS Virtualization framework, not the `container` implementation or configuration.
- To release memory back to macOS, you must **restart** the container, forcing the VM to terminate and release its resources.
- The `--memory` flag (documented in [`docs/command-reference.md`](https://github.com/apple/container/blob/main/docs/command-reference.md)) sets a maximum cap but does not influence reclamation behavior.

## Frequently Asked Questions

### Why does my container VM hold onto memory after processes terminate?

Because the macOS Virtualization framework does not implement the memory reclamation portion of the ballooning protocol. When the guest Linux kernel frees pages internally, the framework keeps the physical memory mapped to the VM. According to the technical documentation in [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md), this is expected behavior for the current framework implementation.

### Is there a command to force a container to release memory without restarting?

No. The `container` CLI does not provide a command to shrink a running VM's memory allocation because the underlying Virtualization framework does not expose functionality to return memory from guest to host. The only method to reclaim the memory is to stop the container (which destroys the VM) and start it again.

### Does the `--memory` flag control how much RAM is returned to macOS?

No. The `--memory` flag, parsed by [`Sources/ContainerPersistence/MemorySize.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MemorySize.swift), only establishes an upper bound on how much memory the VM may allocate from the host. It has no effect on memory reclamation; the VM will maintain its peak memory allocation regardless of this setting until the process terminates.

### Will this limitation be resolved in future container updates?

Resolution depends on Apple extending the Virtualization framework to support full bidirectional memory ballooning. Since the limitation exists at the framework level rather than in the `container` source code, the project cannot unilaterally fix this behavior. Users should monitor Apple's Virtualization framework release notes for updates to memory management capabilities.