# Memory Ballooning in Apple Container: How It Works and Its Limitations

> Discover how memory ballooning works in Apple Container for Linux VMs. Learn its capabilities and limitations, including memory reclamation challenges without restarts.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: internals
- Published: 2026-06-14

---

**Memory ballooning in Apple Container allows Linux VMs to declare a maximum memory ceiling while consuming only active pages, but the Virtualization framework cannot reclaim freed memory without a container restart.**

The `apple/container` runtime executes containers inside lightweight Linux virtual machines managed by the macOS Virtualization framework. While this architecture supports **memory ballooning** to optimize host resource utilization, the implementation contains specific constraints regarding how memory is returned to the host system.

## How Memory Ballooning Works in Apple Container

### VM Configuration and Memory Allocation

When creating a container, the `--memory` flag (e.g., `--memory 16g`) defines the upper bound stored in the VM's boot configuration. In [`Sources/ContainerCommands/Machine/MachineCreate.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Machine/MachineCreate.swift) (lines 61-63), the CLI parses this argument and injects it into the `MachineConfig` before the VM starts:

```swift
@Option(name: .long, help: "Memory allocation (e.g., 2G, 8G). Default: half of system memory")
public var memory: String?

// Later injection into boot configuration
if let memory {
    bootConfig["memory"] = memory
}

```

### Dynamic Memory Management Inside the Guest

The Linux guest OS manages its own memory allocation through the standard kernel page allocator. When processes free memory, the guest's balloon driver can mark pages for release, but the actual return to the host depends on the Virtualization framework's partial API implementation.

### Host-Side Memory Observation

The actual RAM consumed by the VM often differs significantly from the declared limit. According to [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md) (lines 57-60), a container launched with `--memory 16g` may only occupy approximately 2 GiB on the host. You can verify current usage via `container machine inspect`:

```bash
container machine inspect $(container machine list --quiet) \
  | grep -i memory | awk '{print $2}' | while read bytes; do
    echo "VM uses $(bc <<< "scale=2; $bytes/1024/1024/1024") GiB"
  done

```

## Limitations of Memory Ballooning in Container

The current implementation imposes three critical constraints that affect production workloads and host resource management.

### Partial Framework Support

The Virtualization framework implements only a **partial ballooning API**. While the VM can grow memory on demand to accommodate workload spikes, it cannot fully return freed pages to macOS. Memory pages released by the Linux guest remain allocated to the VM process from the host's perspective, even when the guest reports them as available.

### No Automatic Memory Reclamation

Freed pages inside the guest are **not relinquished** to the host automatically. If a container allocates 8 GiB for a temporary operation and then frees it, the host retains the allocation unless you restart the container or the entire VM. This behavior makes memory ballooning effectively "grow-only" in practice, risking host memory saturation when running multiple memory-intensive containers.

### Static Minimum Allocation

The runtime enforces a floor of **200 MiB** per container regardless of actual usage. In [`Sources/ContainerResource/Container/ContainerConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Container/ContainerConfiguration.swift) (lines 151-156), the `ContainerConfiguration` class defines this minimum threshold, preventing containers from ballooning below this baseline even when completely idle.

## Practical Examples and Verification

To launch a container with a generous memory limit without immediately consuming host resources:

```bash

# Launch with 16GiB ceiling, but minimal initial host usage

container run -d --name big-app --memory 16g myimage:latest

```

To demonstrate the reclamation limitation:

```bash

# Allocate and free 8GiB inside the container

container exec big-app -- bash -c '
  dd if=/dev/zero of=/tmp/bigfile bs=1M count=8192
  rm /tmp/bigfile
'

# Host memory usage remains unchanged

# To force reclamation, restart the container:

container stop big-app && container start big-app

```

## Key Implementation Files

- **[`Sources/ContainerCommands/Machine/MachineCreate.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Machine/MachineCreate.swift)**: Handles the `--memory` CLI flag and VM boot configuration injection.
- **[`Sources/ContainerResource/Container/ContainerConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Container/ContainerConfiguration.swift)**: Enforces the 200 MiB minimum memory allocation.
- **[`Sources/ContainerPersistence/MachineConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MachineConfig.swift)**: Defines the `MemorySize` type and default memory handling for VM configuration.
- **[`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift)**: Exposes memory usage statistics via `stats.memory?.usageBytes`.
- **[`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md)**: Documents ballooning behavior and current limitations.

## Summary

- Memory ballooning allows declaring a high memory ceiling (`--memory`) while consuming only actively used pages on the host.
- The Virtualization framework provides only **partial support**—VMs can grow but cannot effectively shrink from the host perspective.
- **Freed memory is not automatically reclaimed** by the host; container or VM restarts are required to release pages back to macOS.
- A **200 MiB minimum allocation** is enforced per container, preventing ballooning below this threshold.
- Monitor actual usage via `container machine inspect` rather than relying on the configured limit.

## Frequently Asked Questions

### Does Apple Container support automatic memory ballooning?

No. While the VM can request additional memory from the host as workloads demand it, the Virtualization framework lacks the API hooks to automatically return freed pages to macOS. The ballooning mechanism is effectively one-way—growing on demand but requiring a restart to shrink.

### Why doesn't freed memory return to the host immediately?

The Linux guest may free pages internally through its standard memory management, but the Virtualization framework's partial implementation cannot relinquish these pages back to the macOS host. The pages remain allocated to the VM process, visible as sustained memory usage in Activity Monitor even when the guest OS reports them as free.

### What is the minimum memory allocation for a container?

The runtime enforces a minimum of **200 MiB** per container. This threshold is hardcoded in [`ContainerConfiguration.swift`](https://github.com/apple/container/blob/main/ContainerConfiguration.swift) and applies regardless of how little memory the workload actually consumes, ensuring a baseline reservation for each VM.

### How can I check actual memory usage versus the configured limit?

Use `container machine inspect` to retrieve the current memory footprint in bytes from [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift). Compare this value against the `--memory` flag set during container creation. The actual usage represents what the host has allocated to the VM process, while the configured limit represents the VM's maximum growth potential.