How Container Handles Memory Management and Ballooning Limitations on macOS
Apple Container allocates memory upfront to Linux VMs but cannot return freed pages to macOS due to partial Virtualization framework support, requiring periodic restarts to reclaim host memory.
The container project from Apple runs each workload inside its own lightweight Linux virtual machine using the Apple Virtualization framework. When you specify a memory limit with the --memory flag, the underlying VM receives that amount of RAM at startup. However, the framework's incomplete memory ballooning implementation creates specific constraints that affect long-running container operations on macOS.
Upfront Memory Allocation in Linux VMs
Each container operates within an isolated Linux VM configured through ContainerConfiguration.Resources. The memoryInBytes property defines the VM's total RAM allocation, which is reserved at startup rather than allocated dynamically.
// Sources/ContainerResource/Container/ContainerConfiguration.swift
public var memoryInBytes: UInt64 = 1024.mib() // default 1 GiB
Unlike traditional container runtimes that share the host kernel, container provisions the full memory amount to the guest VM immediately. This design ensures strict isolation but means that macOS cannot reclaim unused portions of that allocation while the VM runs, regardless of actual usage inside the container.
Partial Memory Ballooning and Host Retention
The Apple Virtualization framework provides only partial support for memory ballooning. While the Linux guest can free pages internally when processes terminate, those physical pages are not relinquished to the host according to the documentation in docs/technical-overview.md:
"Currently, memory pages freed to the Linux operating system by processes running in the container's VM are not relinquished to the host."
This creates three critical operational constraints:
- Memory accumulation: A container that temporarily spikes to 16 GiB will retain that host memory allocation even after internal usage drops to zero.
- Restart requirement: To return memory to macOS, you must stop or restart the entire container VM.
- Host pressure: Running many memory-intensive containers can exhaust physical RAM despite low active usage inside each guest.
Minimum Memory Validation
To prevent unbootable configurations, ContainersService enforces a hard floor of 200 MiB during container creation. Any request below this threshold fails immediately with a clear error message.
// Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift
let minimumMemory: UInt64 = 200.mib()
guard configuration.resources.memoryInBytes >= minimumMemory else {
throw ContainerizationError(
.invalidArgument,
message: "minimum memory amount allowed is 200 MiB (got \(configuration.resources.memoryInBytes) bytes)"
)
}
This validation ensures the Linux VM has sufficient resources to initialize the kernel and basic system services.
Parsing Memory Limits from the CLI
The command-line interface accepts human-readable memory strings (e.g., 256m, 2g, 8G) and converts them to bytes via Parser.swift.
// Sources/Services/ContainerAPIService/Client/Parser.swift
public static func memoryStringAsMiB(_ memory: String) throws -> Int64 {
let ram = try Measurement.parse(parsing: memory) // supports K, M, G, T, P suffixes
// ...
}
You can allocate specific amounts per container or configure the underlying container machine that hosts all containers:
# Allocate 4 GiB to a specific container
container run --rm --memory 4g myimage:latest
# Set default memory for the container machine (VM host)
container machine set --name dev memory=8G
Practical Workarounds for Memory Pressure
Given the ballooning limitations, operators should adopt specific strategies to prevent host memory exhaustion:
- Periodic restarts: Schedule container restarts during maintenance windows to force memory back to macOS, as this is the only mechanism to release pages to the host.
- Right-sizing limits: Avoid over-provisioning; use the lowest viable memory value for the workload to minimize the upfront allocation.
- Machine-level controls: Adjust the underlying container machine memory using
container machine setto establish host-wide boundaries that prevent individual containers from monopolizing resources. - Inspection: Verify current allocations using
container inspect foo --format "{{ .Resources.MemoryInBytes }}"to identify high-memory containers.
Summary
containerallocates VM memory upfront viaContainerConfiguration.Resources.memoryInBytes, defaulting to 1 GiB.- The Apple Virtualization framework provides only partial ballooning support; freed pages remain allocated to the VM on the host until the VM stops.
- A hard minimum of 200 MiB is enforced in
ContainersService.swiftto ensure VM bootability. - Memory strings are parsed in
Parser.swiftsupporting K, M, G, T, and P suffixes. - Restarting containers is currently the only method to return freed memory to macOS.
Frequently Asked Questions
Why doesn't freed container memory return to macOS?
The Apple Virtualization framework implements only partial memory ballooning. While the Linux guest can mark pages as free internally, the framework does not return those physical pages to the host operating system. Consequently, the VM retains its peak memory allocation regardless of current guest usage.
What is the minimum memory required for a container?
container enforces a minimum of 200 MiB per container, validated in Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift. Requests below this threshold receive an invalidArgument error with the message "minimum memory amount allowed is 200 MiB".
How can I reclaim memory from a stopped container on macOS?
You must completely stop or restart the container VM. Simply stopping a process inside the container frees memory within the Linux guest but does not return it to macOS; only terminating the VM releases the allocation back to the host system.
Can I adjust memory limits on a running container?
No, memory limits are immutable after VM creation. To change the memory allocation, you must stop the container and create a new one with the updated --memory flag, or adjust the container machine memory using container machine set and restart the entire machine.
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 →