# How to Optimize Container Memory Usage in Apple Container: Managing Ballooning Limitations

> Optimize container memory usage with explicit limits and stats monitoring. Learn to manage ballooning limitations in Apple's container framework for better performance.

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

---

**Optimize container memory usage by explicitly setting `--memory` limits, monitoring with `container stats`, and restarting containers after heavy workloads, because the macOS Virtualization framework only provides partial ballooning that never returns freed pages to the host.**

The `apple/container` project runs Linux containers inside lightweight VMs on macOS using the native Virtualization framework. While this delivers near-native performance, the underlying memory management has a critical constraint: the framework's **partial memory ballooning** allows the guest kernel to reclaim unused pages internally, but those pages are never released back to the host macOS system. Understanding this limitation is essential for preventing host memory exhaustion.

## Understanding the Ballooning Limitation

According to the `apple/container` source code, the macOS Virtualization framework offers incomplete memory ballooning support. As documented in [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md) (lines 57-59), when the guest Linux kernel releases memory pages, the VM's reserved allocation **remains held by the host** indefinitely.

This creates a one-way memory trap:
- The VM can shrink its apparent RAM usage for the guest OS
- The host macOS never reclaims the physical pages
- Memory pressure on the host increases even when containers appear idle

The only mechanism to free reserved RAM is to **destroy and recreate the container** (and its underlying VM).

## The Default Memory Allocation Problem

By default, `container` over-provisions memory aggressively. In [`Sources/ContainerPersistence/MachineConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MachineConfig.swift), the `defaultMemory` calculation assigns **half of the host's physical memory** (capped at 1 GiB) to each new container VM.

This design means:
- A Mac with 32 GB RAM allocates 16 GB to a single container by default
- Idle containers retain that reservation permanently due to the ballooning limitation
- Running multiple containers quickly exhausts physical memory without surfacing obvious usage in Activity Monitor

## Optimization Strategies

### Explicitly Size Containers with `--memory`

Always override the default allocation using the `--memory` flag or the `machine create --memory` option. The `MemorySize` type in [`Sources/ContainerPersistence/MemorySize.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MemorySize.swift) accepts human-readable strings like `"2g"` or `"512mb"`.

```bash

# Cap container at 2 GiB instead of half system RAM

container run --memory 2g myimage:latest

# Create a machine with specific limits

container machine create --memory 4g mymachine

```

For most development workloads, **2 GiB provides sufficient headroom** while preventing unnecessary host memory pressure.

### Monitor Memory Usage with `container stats`

Verify actual consumption before adjusting limits. The `container stats` command reports live "Memory Usage" metrics, as validated in [`Tests/IntegrationTests/Containers/TestCLIStatsCommand.swift`](https://github.com/apple/container/blob/main/Tests/IntegrationTests/Containers/TestCLIStatsCommand.swift).

```bash

# Check real-time memory consumption

container stats

```

If usage consistently stays below 50% of the allocated limit, reduce the `--memory` value to free capacity for other containers.

### Restart Containers After Heavy Workloads

When a container temporarily spikes to high memory usage (e.g., during builds or data processing), the ballooning limitation prevents automatic reclamation. **Schedule periodic restarts** for long-running containers to return reserved pages to the host.

```bash

# Stop and restart to free held memory

container stop mycontainer
container start mycontainer

```

### Avoid Over-Commit on Single Host

Keep the sum of all active container `--memory` limits below the host's physical RAM. Because freed pages aren't reclaimed, over-committing causes immediate memory pressure that can degrade system performance or trigger swap usage.

## Implementation Examples

### CLI Configuration

Set realistic bounds at runtime:

```bash

# Development web server with modest needs

container run -d --memory 512mb --name web nginx:alpine

# Java application with higher requirements

container run -d --memory 4g --name backend openjdk:17

```

### Swift API Integration

When programmatically managing containers, use the `MemorySize` type directly:

```swift
import ContainerPersistence

do {
    // Parse human-readable memory strings
    let memory = try MemorySize("2g")
    let config = MachineConfig(memory: memory)
    try config.save(to: .defaultPath)
    
    // Or use with Resources for direct API calls
    let resources = Resources(memory: try MemorySize("2048mb"))
    client.startContainer(resources: resources, image: imageRef)
} catch {
    print("Invalid memory specification: \(error)")
}

```

The `MemorySize` struct stores values as `Measurement<UnitInformationStorage>`, ensuring type-safe arithmetic across the codebase.

## Key Source Files

- **[`Sources/ContainerPersistence/MemorySize.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MemorySize.swift)**: Parses memory strings and handles unit conversions
- **[`Sources/ContainerPersistence/MachineConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/MachineConfig.swift)**: Defines default memory allocation logic (`defaultMemory`)
- **[`Sources/ContainerPersistence/ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ContainerSystemConfig.swift)**: Provides runtime defaults (e.g., `2048MB` base values)
- **[`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md)**: Documents the partial ballooning limitation (lines 57-59)
- **[`Tests/IntegrationTests/Containers/TestCLIStatsCommand.swift`](https://github.com/apple/container/blob/main/Tests/IntegrationTests/Containers/TestCLIStatsCommand.swift)**: Validates memory usage reporting

## Summary

- **Partial ballooning** in the macOS Virtualization framework prevents the host from reclaiming memory released by the guest Linux kernel
- **Default allocations** reserve half the host's RAM (up to 1 GiB) per container, which remain held indefinitely
- **Explicit sizing** with `--memory` flags prevents over-provisioning and keeps host memory available
- **Monitoring** via `container stats` reveals actual usage patterns for right-sizing decisions
- **Restarting** containers is the only method to return reserved memory to the host after spikes

## Frequently Asked Questions

### Why doesn't memory usage decrease in Activity Monitor when containers become idle?

The macOS Virtualization framework's ballooning implementation allows the Linux guest to reclaim unused pages internally, but those pages are never returned to the host macOS system. As documented in [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md), the host retains the entire VM reservation regardless of guest activity. You must restart the container to release the physical memory.

### How do I pick the right `--memory` value for my container?

Start with **2 GiB** for most development workloads, then monitor with `container stats`. If the process consistently uses less than 50% of the allocation, reduce the limit. For memory-intensive builds or database containers, test with progressively higher values (e.g., `4g`, `8g`) while monitoring the "Memory Usage" column to find the peak working set.

### What happens if I set `--memory` higher than available host RAM?

The container will fail to start or cause immediate system memory pressure. Unlike Linux cgroups, the macOS Virtualization framework reserves the full allocation upfront due to the ballooning limitation. Never set the sum of all container memory limits above your Mac's physical RAM capacity.

### Can I change the memory limit of a running container?

No, the memory limit is configured at VM creation time in [`MachineConfig.swift`](https://github.com/apple/container/blob/main/MachineConfig.swift) and cannot be modified dynamically. You must stop the container and recreate it with a new `--memory` value. This restart also serves to reclaim any memory that was temporarily ballooned but not released to the host.