# How Resource Limits (CPU, Memory, and Ulimits) Are Implemented in Apple Container

> Discover how Apple Container implements CPU, memory, and ulimits resource limits using a three layer architecture with cgroups and setrlimit system calls. Learn more now.

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

---

**Apple Container enforces resource limits through a three‑layer architecture: CLI flags are parsed into typed Swift structures in [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift), serialized into JSON‑encoded `ProcessConfiguration` messages, and enforced inside the Linux VM via cgroups for CPU and memory while POSIX ulimits are applied through `setrlimit` system calls.**

The apple/container repository implements container resource constraints by bridging macOS client code with a Linux‑based runtime. When you pass `--cpus`, `--memory`, or `--ulimit` flags to the `container run` command, the system translates these user‑friendly strings into kernel‑level restrictions through a well‑defined pipeline of validation, serialization, and system‑call execution.

## The Three‑Layer Resource Limit Architecture

Resource enforcement operates across distinct architectural boundaries:

- **CLI and Client Layer**: Parses `--ulimit`, `--cpus`, and `--memory` arguments in [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and validates them in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift)
- **Configuration Serialization**: Encodes limits into `ProcessConfiguration.Rlimit` and `ContainerConfiguration.Resources` structs that travel over XPC as JSON
- **VM Runtime Layer**: The Linux‑based runtime inside the VM unpacks the JSON and applies cgroups for CPU/memory or invokes `setrlimit` for POSIX ulimits before exec‑ing the container process

This separation ensures that Swift code handles user input and validation while the VM‑side runtime performs privileged kernel operations.

## Parsing Ulimits from the Command Line

### Defining CLI Flags in Flags.swift

The entry point for resource limits begins in the command‑line interface definition. In [`Sources/Services/ContainerAPIService/Client/Flags.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Flags.swift), the `Flags` struct declares the `ulimits` array that captures repeated `--ulimit` flags:

```swift
public var ulimits: [String] = []        // populated from "--ulimit" on the CLI

```

### Mapping and Validation in Parser.swift

The heavy lifting of validation and type conversion happens in [`Sources/Services/ContainerAPIService/Client/Parser.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Parser.swift). The parser maintains a strict mapping between human‑readable ulimit names and Linux `RLIMIT` constants:

```swift
private static let ulimitNameToRlimit: [String: String] = [
    "cpu":    "RLIMIT_CPU",
    "fsize":  "RLIMIT_FSIZE",
    "data":   "RLIMIT_DATA",
    "stack":  "RLIMIT_STACK",
    "core":   "RLIMIT_CORE",
    "rss":    "RLIMIT_RSS",
    "nproc":  "RLIMIT_NPROC",
    "nofile": "RLIMIT_NOFILE",
    "memlock":"RLIMIT_MEMLOCK",
    "as":     "RLIMIT_AS",
    "locks":  "RLIMIT_LOCKS",
    "sigpending": "RLIMIT_SIGPENDING",
    "msgqueue":   "RLIMIT_MSGQUEUE",
    "nice":       "RLIMIT_NICE",
    "rtprio":     "RLIMIT_RTPRIO",
    "rttime":     "RLIMIT_RTTIME"
]

```

The `rlimit(_:)` method enforces rigorous validation rules:

1. **Syntax checking**: Ensures the input matches `<type>=<soft>[:<hard>]`
2. **Type validation**: Verifies the type exists in `ulimitNameToRlimit`
3. **Value parsing**: Accepts non‑negative integers or the literal **`unlimited`**
4. **Constraint validation**: Guarantees soft limit ≤ hard limit
5. **Duplicate detection**: Prevents specifying the same limit type twice

Upon successful validation, the method returns a `ProcessConfiguration.Rlimit` value:

```swift
public static func rlimit(_ ulimit: String) throws -> ProcessConfiguration.Rlimit {
    // Split "type=soft[:hard]"
    let parts = ulimit.split(separator: "=", maxSplits: 1)
    guard parts.count == 2 else {
        throw ContainerizationError(.invalidArgument,
            message: "invalid ulimit format '\(ulimit)': expected <type>=<soft>[:<hard>]")
    }

    let typeName = String(parts[0])
    guard let rlimitType = ulimitNameToRlimit[typeName] else {
        let valid = ulimitNameToRlimit.keys.sorted().joined(separator: ", ")
        throw ContainerizationError(.invalidArgument,
            message: "unsupported ulimit type '\(typeName)': valid types are \(valid)")
    }

    // Parse soft / hard values (allow "unlimited")
    let valueParts = parts[1].split(separator: ":", maxSplits: 1)
    let soft = try parseRlimitValue(String(valueParts[0]), typeName: typeName)
    let hard = (valueParts.count == 2)
        ? try parseRlimitValue(String(valueParts[1]), typeName: typeName)
        : soft

    guard soft <= hard else {
        throw ContainerizationError(.invalidArgument,
            message: "ulimit '\(typeName)' soft limit (\(soft)) cannot exceed hard limit (\(hard))")
    }

    return ProcessConfiguration.Rlimit(limit: rlimitType, soft: soft, hard: hard)
}

```

## Configuring CPU and Memory Limits

Unlike ulimits, CPU and memory constraints are container‑wide resources rather than per‑process limits. These values bypass the POSIX rlimit mechanism and instead target Linux cgroups.

### Container‑Wide Defaults

Default resource values reside in [`Sources/ContainerPersistence/ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ContainerSystemConfig.swift):

```swift
public struct ContainerSystemConfig: Sendable, Codable {
    public let cpus: Int                     // default: 4
    public let memory: MemorySize            // default: 1 GiB (1024 MiB)
}

```

### Per‑Container Resource Specifications

When users specify `--cpus` or `--memory` on the command line, these populate the `Resources` struct inside `ContainerConfiguration`, defined in [`Sources/ContainerResource/Container/ContainerConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Container/ContainerConfiguration.swift):

```swift
public struct ContainerConfiguration: Sendable, Codable {
    public var resources: Resources          // includes cpus, memoryInBytes, …
}

```

The runtime uses these values to configure the cgroup controller for the container process, ensuring hard caps on CPU cores and memory consumption.

## Runtime Enforcement Inside the VM

### XPC Message Handling in RuntimeService.swift

Once the client validates and packages the configuration, it sends an XPC message to the runtime service. In [`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift), the `createProcess(_:)` method deserializes the JSON and extracts the resource limits:

```swift
public func createProcess(_ message: XPCMessage) async throws -> XPCMessage {
    …
    let config = try message.processConfig()   // ← JSON → ProcessConfiguration
    …
    try await self.addNewProcess(id, config, stdio) // passes the Rlimit array
    …
}

```

The `ProcessConfiguration` struct, defined in [`Sources/ContainerResource/Container/ProcessConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Container/ProcessConfiguration.swift), carries the ulimits as an array of `Rlimit` objects:

```swift
public struct Rlimit: Sendable, Codable {
    /// The Rlimit type of the Process (e.g. "RLIMIT_NOFILE").
    public let limit: String
    /// Soft limit (the value a process may raise to its hard limit without privilege).
    public let soft: UInt64
    /// Hard limit (the absolute maximum).
    public let hard: UInt64
}

```

### Linux System Call Execution

Inside the VM, the `container-runtime-linux` component receives the `ProcessConfiguration` JSON. For each entry in the `rlimits` array, it:

1. Maps the `limit` string (e.g., `"RLIMIT_NOFILE"`) to the corresponding Linux constant
2. Invokes the `setrlimit(2)` system call with the soft and hard values
3. Applies CPU and memory constraints via the cgroup v2 controllers before exec‑ing the user binary

This ensures that by the time the containerized process starts, all resource boundaries are active and enforced by the kernel.

## Practical Examples

### Running Containers with Custom Limits

To start a container with specific CPU, memory, and file descriptor constraints:

```bash

# Give the container a higher file-descriptor limit and a capped process count

container run -n demo \
    --ulimit nofile=4096:8192 \
    --ulimit nproc=256 \
    --cpus 2 \
    --memory 2G \
    alpine sh -c 'ulimit -n; ulimit -u'

```

**Expected output** inside the container:

```

4096
256

```

### Using the Swift API Directly

For programmatic control, construct the configuration objects directly:

```swift
import ContainerAPIService

let flags = ContainerAPIServiceClient.Flags(
    cpus: 2,
    memory: "2G",
    ulimits: ["nofile=4096:8192", "nproc=256"]
)

let client = try ContainerAPIServiceClient()
let config = try client.createConfiguration(from: flags)

let processConfig = ProcessConfiguration(
    executable: "/bin/sh",
    arguments: ["-c", "ulimit -n; ulimit -u"],
    environment: [],
    workingDirectory: "/",
    terminal: false,
    user: .id(uid: 0, gid: 0),
    supplementalGroups: [],
    rlimits: config.rlimits           // <-- populated from `flags.ulimits`
)

try await client.createProcess(configuration: processConfig)

```

### Inspecting Limits at Runtime

Retrieve current resource statistics to verify enforcement:

```swift
let stats = try await client.getStatistics(containerName: "demo")
print("CPU usage (µs):", stats.cpuUsageUsec ?? "‑")
print("Memory used (MiB):", stats.memoryUsageBytes.map { $0 / (1024*1024) } ?? "‑")
print("RLIMIT_NOFILE soft:", stats.processRlimits.first { $0.limit == "RLIMIT_NOFILE" }?.soft ?? "‑")

```

## Testing Resource Limit Enforcement

The implementation includes comprehensive test coverage to verify the end‑to‑end flow:

- **Integration tests** in [`Tests/IntegrationTests/Run/TestCLIRunCommand.swift`](https://github.com/apple/container/blob/main/Tests/IntegrationTests/Run/TestCLIRunCommand.swift) launch actual containers with `--ulimit nofile=1024:2048` and assert that `ulimit -n` inside the container returns the expected value
- **Parser unit tests** in [`Tests/ContainerAPIClientTests/ParserTest.swift`](https://github.com/apple/container/blob/main/Tests/ContainerAPIClientTests/ParserTest.swift) exercise error handling for malformed syntax, unsupported limit types, and soft‑hard constraint violations

These tests ensure that validation errors surface at the client layer while enforcement failures are caught during runtime integration.

## Summary

- **CLI parsing** happens in [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift), mapping human‑readable strings to `ProcessConfiguration.Rlimit` objects and `ContainerConfiguration.Resources`
- **Validation** occurs before any VM communication, checking syntax, limit types, value ranges, and soft‑hard relationships
- **Serialization** converts Swift structs into JSON‑encoded XPC messages sent to `RuntimeService.createProcess(_:)`
- **Enforcement** splits by resource type: cgroups control CPU and memory at the container level, while `setrlimit` applies POSIX ulimits to the individual process inside the VM
- **Source files** [`ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/ContainerSystemConfig.swift), [`ProcessConfiguration.swift`](https://github.com/apple/container/blob/main/ProcessConfiguration.swift), and [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift) form the critical path from user input to kernel constraints

## Frequently Asked Questions

### What is the correct syntax for the `--ulimit` flag?

The `--ulimit` flag requires the format `<type>=<soft>[:<hard>]`, where type is a POSIX resource name (e.g., `nofile`, `nproc`, `stack`), soft is the current limit, and hard is the absolute ceiling. If omitted, the hard limit defaults to match the soft limit. Both limits accept `unlimited` or non‑negative integers, and the soft limit cannot exceed the hard limit according to the validation logic in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift).

### How do CPU and memory limits differ from ulimits?

CPU and memory limits are container‑wide constraints enforced via Linux cgroups, configured through `ContainerConfiguration.Resources` and defaulting in [`ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/ContainerSystemConfig.swift). Ulimits are per‑process POSIX restrictions enforced by the `setrlimit` system call, stored as an array of `Rlimit` structs in `ProcessConfiguration`, and applied to the specific process inside the VM before execution begins.

### Why does the parser reject duplicate ulimit types?

The parser in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift) explicitly detects and rejects duplicate ulimit specifications to prevent ambiguous configuration. Since each resource type can only have one soft and one hard limit, specifying the same type twice would create undefined behavior in the runtime, so the client throws a validation error before sending the configuration to the VM.

### At what point are resource limits actually enforced?

Limits are enforced inside the VM after the Swift client sends the JSON configuration via XPC to `RuntimeService.createProcess(_:)`. The VM‑side runtime unpacks the `ProcessConfiguration`, translates `Rlimit` strings into Linux constants, and invokes `setrlimit` for ulimits or configures cgroups for CPU and memory immediately before calling `exec` to start the containerized binary.