# How to Set Container Resource Limits and ulimits Configuration in Apple Container

> Learn to set container resource limits and ulimits in Apple Container using the --ulimit flag for efficient resource management and performance.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: how-to-guide
- Published: 2026-06-29

---

**You configure container resource limits in Apple Container by passing the `--ulimit` flag with the format `<type>=<soft>[:<hard>]`, which the runtime translates into POSIX rlimit structures applied when the process starts.**

The Apple Container framework provides granular control over process resource consumption through ulimits configuration. By translating the `--ulimit` command-line flag into POSIX rlimit structures, the runtime enforces hard ceilings on file descriptors, memory, CPU time, and other kernel resources when spawning container processes. This implementation spans the CLI flag definition, parsing logic, and the data structures that the container runtime consumes.

## Understanding the --ulimit Flag and CLI Syntax

The `--ulimit` option is declared in the CLI flag struct to accept multiple string values representing resource constraints.

In [`Sources/Services/ContainerAPIService/Client/Flags.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Flags.swift) (lines 91‑98), the flag is defined as:

```swift
@Option(name: .customLong("ulimit") … ) public var ulimits: [String] = []

```

When you invoke the container command, each `--ulimit` argument follows the pattern `<type>=<soft>[:<hard>]`. If you omit the hard limit, the runtime automatically sets it equal to the soft limit. For example, `nofile=1024:2048` sets a soft limit of 1024 open files and a hard limit of 2048.

## Parsing ulimits in the Source Code

When the command executes, the raw strings from `flags.ulimits` are handed to `Parser.rlimits(_:)` in [`Sources/Services/ContainerAPIService/Client/Parser.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Parser.swift) (lines 908‑921). This method iterates over the input array, parses each entry, and validates that no duplicate limit types exist.

```swift
public static func rlimits(_ rawUlimits: [String]) throws -> [ProcessConfiguration.Rlimit] {
    var rlimits: [ProcessConfiguration.Rlimit] = []
    rlimits.reserveCapacity(rawUlimits.count)
    var seenTypes: Set<String> = []

    for ulimit in rawUlimits {
        let rlimit = try Parser.rlimit(ulimit)          // parse a single entry
        if seenTypes.contains(rlimit.limit) {
            throw ContainerizationError(.invalidArgument,
                message: "duplicate ulimit type: \(ulimit.split(separator: \"=\").first ?? \"\")")
        }
        seenTypes.insert(rlimit.limit)
        rlimits.append(rlimit)
    }
    return rlimits
}

```

### Mapping User-Friendly Names to Kernel Constants

The parser converts textual type names (like `nofile` or `nproc`) into kernel constants (like `RLIMIT_NOFILE` or `RLIMIT_NPROC`) using a static dictionary defined in [`Sources/Services/ContainerAPIService/Client/Parser.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/Parser.swift) (lines 908‑925):

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

```

### Validating Soft and Hard Limits

The single-entry parsing logic in `Parser.rlimit(_:)` (lines 953‑1014) splits each string on the `=` character, validates the format, and ensures the soft limit does not exceed the hard limit:

```swift
public static func rlimit(_ ulimit: String) throws -> ProcessConfiguration.Rlimit {
    // <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]).lowercased()
    let valuesPart = String(parts[1])

    // Map the textual name to the kernel constant
    guard let rlimitType = ulimitNameToRlimit[typeName] else {
        let validTypes = ulimitNameToRlimit.keys.sorted().joined(separator: ", ")
        throw ContainerizationError(.invalidArgument,
            message: "unsupported ulimit type '\(typeName)': valid types are \(validTypes)")
    }

    // Split soft/hard values
    let valueParts = valuesPart.split(separator: ":", maxSplits: 1)
    let soft: UInt64
    let hard: UInt64
    switch valueParts.count {
    case 1:
        soft = try parseRlimitValue(String(valueParts[0]), typeName: typeName)
        hard = soft
    case 2:
        soft = try parseRlimitValue(String(valueParts[0]), typeName: typeName)
        hard = try parseRlimitValue(String(valueParts[1]), typeName: typeName)
    default:
        throw ContainerizationError(.invalidArgument,
            message: "invalid ulimit format '\(ulimit)': expected <type>=<soft>[:<hard>]")
    }

    // Validate soft ≤ hard
    if soft > hard {
        throw ContainerizationError(.invalidArgument,
            message: "ulimit '\(typeName)' soft limit (\(soft)) cannot exceed hard limit (\(hard))")
    }

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

```

## Storing Limits in ProcessConfiguration

Each parsed limit is stored as a `ProcessConfiguration.Rlimit` value. In [`Sources/ContainerResource/Container/ProcessConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/ContainerResource/Container/ProcessConfiguration.swift) (lines 34‑48), the structure is defined as:

```swift
public struct ProcessConfiguration: Sendable, Codable {
    // ...
    public var rlimits: [Rlimit]
    // ...
    public struct Rlimit: Sendable, Codable {
        public let limit: String   // e.g. "RLIMIT_NOFILE"
        public let soft: UInt64
        public let hard: UInt64
    }
}

```

When the container launches, the runtime reads `ProcessConfiguration.rlimits` and invokes the appropriate system API (e.g., `setrlimit(2)`) for each entry, thereby enforcing the requested ceilings on file descriptors, processes, memory-lock size, and other resources.

## Practical Implementation Examples

### Command-Line Usage

Use the `--ulimit` flag multiple times to set different resource constraints:

```bash

# Limit the container to 1024 open files (soft) and 2048 (hard)

container run --ulimit nofile=1024:2048 -it ubuntu:latest

# Unlimited core dump size

container run --ulimit core=unlimited -it alpine:latest

# Set max number of processes to 512 (soft = hard)

container run --ulimit nproc=512 -d myimage

```

### Programmatic Configuration in Swift

You can also construct `ProcessConfiguration` directly in Swift code:

```swift
import ContainerResource

// Build a ProcessConfiguration manually
let rlimit = ProcessConfiguration.Rlimit(
    limit: "RLIMIT_NOFILE",   // same keys used in the parser dictionary
    soft: 1024,
    hard: 2048
)

let cfg = ProcessConfiguration(
    executable: "/bin/sh",
    arguments: ["-c", "sleep 3600"],
    environment: [],
    workingDirectory: "/",
    terminal: false,
    user: .id(uid: 1000, gid: 1000),
    supplementalGroups: [],
    rlimits: [rlimit]          // <- inject the limit
)

// Pass `cfg` to the Container API client (e.g. ContainerAPI.run(cfg))

```

## Error Handling and Validation Rules

The parser enforces strict validation rules that surface as `ContainerizationError(.invalidArgument, ...)`:

- **Duplicate types**: If you specify the same ulimit type twice (e.g., `--ulimit nofile=1024 --ulimit nofile=2048`), the runtime throws "duplicate ulimit type: nofile".
- **Unknown type**: If you provide an unsupported type name, the error lists all valid types from the `ulimitNameToRlimit` dictionary.
- **Soft limit exceeds hard limit**: The runtime validates that the soft value is less than or equal to the hard value, throwing "soft limit cannot exceed hard limit" if violated.
- **Invalid values**: Non-numeric values (except the string "unlimited") trigger an error indicating the value must be a non-negative integer or "unlimited".

### Handling Parsing Errors in Swift

```swift
do {
    let limits = try Parser.rlimits(["nofile=1024:2048", "nproc=512"])
    // pass `limits` to ProcessConfiguration
} catch let err as ContainerizationError {
    // display a helpful message to the user
    print("❗️ Invalid ulimit: \(err.message)")
}

```

## Summary

- **Container resource limits and ulimits configuration** in Apple Container use the `--ulimit` flag with the syntax `<type>=<soft>[:<hard>]`.
- The CLI flag is defined in [`Flags.swift`](https://github.com/apple/container/blob/main/Flags.swift) and parsed by `Parser.rlimits(_:)` and `Parser.rlimit(_:)` in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift).
- Textual type names are mapped to kernel constants (e.g., `nofile` → `RLIMIT_NOFILE`) before storage.
- Limits are stored in the `ProcessConfiguration.Rlimit` struct and enforced by the runtime via system calls like `setrlimit(2)`.
- The parser validates against duplicate types, unknown types, and soft limits exceeding hard limits, surfacing clear error messages for each violation.

## Frequently Asked Questions

### What is the correct syntax for setting container resource limits?

The correct syntax is `<type>=<soft>[:<hard>]`, where `type` is a resource name like `nofile` or `nproc`, `soft` is the soft limit, and `hard` is the optional hard limit. If you omit the hard limit, the runtime sets it equal to the soft limit. You can also specify `unlimited` as the value for either limit.

### Which ulimits types are supported by Apple Container?

The framework supports all standard POSIX rlimit types: `core`, `cpu`, `data`, `fsize`, `locks`, `memlock`, `msgqueue`, `nice`, `nofile`, `nproc`, `rss`, `rtprio`, `rttime`, `sigpending`, and `stack`. These are mapped to their respective `RLIMIT_*` constants in the kernel as defined in [`Parser.swift`](https://github.com/apple/container/blob/main/Parser.swift).

### How does the runtime enforce ulimits after parsing?

After parsing, the runtime stores the limits in the `ProcessConfiguration.Rlimit` array. When the container process starts, the runtime iterates over this array and calls the appropriate system API (such as `setrlimit(2)`) to apply each limit to the process, enforcing the constraints at the kernel level.

### What happens if I set a soft limit higher than the hard limit?

The parser in `Parser.rlimit(_:)` explicitly validates that the soft limit is less than or equal to the hard limit. If you violate this constraint (e.g., `--ulimit nofile=2048:1024`), the runtime throws a `ContainerizationError` with the message "ulimit 'nofile' soft limit (2048) cannot exceed hard limit (1024)".