How to Optimize Container Performance with CPU and Memory Limits in Apple Container

Optimize container performance by overriding the default 4 CPU cores and 1 GiB RAM with the --cpus and --memory flags, which enforce limits via Linux cgroups to prevent resource contention and match workload requirements.

The apple/container repository provides a lightweight virtual-machine container runtime that defaults to conservative resource allocation. While these defaults ensure host stability, compute-intensive workloads often require explicit tuning to maximize throughput and prevent throttling.

Understanding Default Resource Limits

By default, each container receives 4 CPU cores and 1 GiB of memory through the Resources struct defined in Sources/ContainerResource/Container/ContainerConfiguration.swift. These values strike a balance between container performance and host responsiveness on typical developer hardware.

The default initialization occurs at lines 53-56, where the struct defines these baseline values:

public struct Resources {
    public var cpus: Int = 4
    public var memoryInBytes: UInt64 = 1 * 1024 * 1024 * 1024  // 1 GiB
    // ...
}

When you optimize container performance with CPU and memory limits, you override these defaults to align resources with specific workload demands.

Configuring Limits via Command Line

The most common method to adjust resources uses the --cpus and --memory flags with container run or container create commands.

To allocate 8 CPUs and 32 GiB of RAM:

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

For builder containers that require additional resources:

container builder start --cpus 4 --memory 8g

The CLI parser converts human-readable memory strings (like 32g) into bytes through the memoryStringAsBytes function in Sources/Services/ContainerAPIService/Client/Parser.swift (lines 49-56). This allows you to specify values using g, m, or k suffixes rather than raw byte counts.

Runtime Enforcement and Validation

Minimum Memory Validation

Before applying limits, the runtime validates that memory allocation meets safety thresholds. In Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift (lines 329-332), a guard clause rejects any container configuration below 200 MiB:

guard config.resources.memoryInBytes >= 200 * 1024 * 1024 else {
    throw ContainerError.invalidMemorySize
}

This prevents accidental undersizing that could cause immediate out-of-memory crashes.

Cgroup Enforcement Mechanism

The actual resource isolation occurs through Linux cgroups. When ContainersService starts a container, it copies the parsed resources into the runtime configuration at Sources/Services/RuntimeLinux/Server/RuntimeService.swift (lines 355-356):

czConfig.memoryInBytes = config.resources.memoryInBytes
czConfig.cpus = config.resources.cpus

The runtime then writes these values to cgroup control files:

  • CPU limits: Written to /sys/fs/cgroup/cpu.max as a quota proportional to the number of CPUs. The denominator defaults to 100,000 microseconds, with the numerator calculated as cpus × 100,000. For example, 8 CPUs results in 800000 100000 in the cgroup file.
  • Memory limits: Written to /sys/fs/cgroup/memory.max in bytes.

You can verify these limits inside a running container by reading the cgroup files directly, as demonstrated in Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift (lines 173-186):

cat /sys/fs/cgroup/cpu.max    # Output: "800000 100000" for 8 CPUs

cat /sys/fs/cgroup/memory.max   # Output: "34359738368" for 32 GiB

Programmatic Resource Configuration

For Swift-based tooling, instantiate ContainerConfiguration.Resources directly to optimize container performance:

import ContainerizationOCI

let resources = ContainerConfiguration.Resources(
    cpus: 8,
    memoryInBytes: 32 * 1024 * 1024 * 1024,  // 32 GiB
    storage: nil,
    cpuOverhead: 1
)

let config = ContainerConfiguration(
    id: "optimized-container",
    image: ImageDescription(name: "my-image"),
    process: ProcessConfiguration(entrypoint: [])
)
config.resources = resources

This approach, defined in ContainerConfiguration.swift (lines 62-68), allows fine-grained control when embedding the container runtime in larger applications.

Summary

  • Default resources: 4 CPUs and 1 GiB RAM defined in ContainerConfiguration.swift
  • CLI flags: Use --cpus and --memory to override defaults, with parsing handled in Parser.swift
  • Safety minimum: 200 MiB memory floor enforced in ContainersService.swift
  • Kernel enforcement: cgroups apply limits via cpu.max and memory.max configured in RuntimeService.swift
  • Verification: Inspect /sys/fs/cgroup/ files to confirm limits are active

Frequently Asked Questions

What are the default CPU and memory limits in Apple Container?

By default, each container receives 4 CPU cores and 1 GiB of memory (1,073,741,824 bytes). These values are hardcoded in the Resources struct within Sources/ContainerResource/Container/ContainerConfiguration.swift to provide a balanced starting point for general development workloads.

How does the container runtime enforce CPU limits?

The runtime enforces CPU limits through Linux cgroups by writing a calculated quota to /sys/fs/cgroup/cpu.max. The quota numerator equals cpus × 100,000 microseconds, while the denominator remains 100,000. For example, 2 CPUs results in 200000 100000 in the cgroup file, limiting the container to proportional time slices of CPU access.

What is the minimum memory limit allowed?

The runtime rejects any container configuration with less than 200 MiB (209,715,200 bytes) of memory. This validation occurs in Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift to prevent configurations that would immediately trigger out-of-memory killer events.

How can I verify resource limits inside a running container?

Inspect the cgroup virtual files directly by running cat /sys/fs/cgroup/cpu.max and cat /sys/fs/cgroup/memory.max inside the container. These files show the actual quota values written by the runtime, confirming that your --cpus and --memory flags were applied correctly.

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 →