How Containers Are Created in Apple Container: CLI to Runtime Architecture

Apple Container creates containers through a three-step pipeline: the CLI parses arguments into a ContainerConfiguration, a ContainerClient sends an XPC message to the daemon, and the server instantiates a sandbox via the OCI runtime.

The Apple Container repository provides a Swift-based container runtime for macOS that bridges user commands with low-level kernel virtualization. Understanding how containers are created in Apple Container reveals a tightly coupled architecture spanning the command line, XPC inter-process communication, and OCI-compliant bundle construction.

The Three-Stage Container Creation Pipeline

Step 1: CLI Parsing and Configuration Assembly

In Sources/ContainerCommands/Container/ContainerCreate.swift, the Application.ContainerCreate subcommand handles user input. The run() method gathers flags and invokes Utility.containerConfigFromFlags() (defined in Sources/ContainerCommands/Container/Utility.swift) to build a complete ContainerConfiguration, kernel specification, and ContainerCreateOptions struct.

Step 2: XPC Transmission to the API Server

The CLI creates a ContainerClient instance (Sources/Services/ContainerAPIService/Client/ContainerClient.swift), which acts as a thin XPC wrapper. It constructs an XPCMessage with the route containerCreate (defined in Sources/ContainerXPC/XPCMessage.swift), encoding the configuration, kernel, and options as JSON payloads before sending the message to com.apple.container.apiserver.

Step 3: Runtime Instantiation and Bundle Creation

The daemon receives the request in Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift, decodes the payload, and delegates to the OCI runtime. It uses Sources/ContainerBuild/Builder.swift to materialize the bundle, create the rootfs, configure cgroups, mount filesystems, and register the container with a generated ID.

Key Implementation Files

File Purpose
Sources/ContainerCommands/Container/ContainerCreate.swift CLI entry point that validates arguments and triggers creation
Sources/ContainerCommands/Container/Utility.swift Helper functions translating flags into ContainerConfiguration
Sources/Services/ContainerAPIService/Client/ContainerClient.swift XPC client wrapper for daemon communication
Sources/ContainerXPC/XPCMessage.swift Wire format and routing enum definitions
Sources/Services/ContainerAPIService/Server/Containers/ContainersService.swift Server-side handler for containerCreate requests
Sources/ContainerBuild/Builder.swift OCI bundle materialization and rootfs setup

Creating Containers via CLI and Swift API

Command Line Interface

The simplest way to create a container uses the container create command:


# Create a container from the "alpine:3.22" image, naming it "my-alpine"

container create alpine:3.22 --name my-alpine

Behind the scenes, Application.ContainerCreate.run() executes:

let client = ContainerClient()
try await client.create(configuration: cfg, options: opts, kernel: kernel, initImage: initImg)
print(id) // prints the generated container ID

Programmatic Creation

For direct integration, use the ContainerClient API:

import ContainerAPIClient
import ContainerResource

let config = try ContainerConfiguration(
    id: "demo-container",
    image: "docker.io/library/alpine:latest",
    command: ["/bin/sh"]
)

let options = ContainerCreateOptions(autoRemove: false)
let kernel = Kernel()
let client = ContainerClient()

Task {
    do {
        try await client.create(
            configuration: config,
            options: options,
            kernel: kernel,
            initImage: nil
        )
        print("Container created")
    } catch {
        print("❗️ failed: \(error)")
    }
}

The client.create() method internally constructs the XPC message:

let request = XPCMessage(route: .containerCreate)
request.set(key: .containerConfig, value: try JSONEncoder().encode(configuration))
request.set(key: .kernel, value: try JSONEncoder().encode(kernel))
request.set(key: .containerOptions, value: try JSONEncoder().encode(options))
try await xpcSend(message: request)

Verifying Container Creation

Inspect the created container using the client:

let client = ContainerClient()
let snapshot = try await client.get(id: "demo-container")
print("State:", snapshot.status.state) // e.g. "running"

Summary

  • Apple Container creates containers through a three-step pipeline: CLI configuration, XPC transmission, and runtime instantiation.
  • The ContainerCreate.swift CLI command builds configurations via Utility.swift before invoking ContainerClient.create().
  • XPC messages use the containerCreate route to communicate with the privileged daemon at com.apple.container.apiserver.
  • The server implementation in ContainersService.swift delegates to Builder.swift for OCI bundle materialization and rootfs setup.
  • Both CLI and programmatic APIs generate a unique container ID and return it to the caller upon successful creation.

Frequently Asked Questions

What is the role of XPC in Apple Container creation?

XPC provides the secure inter-process communication layer between the unprivileged CLI client and the privileged Container-API-Server daemon. The ContainerClient encodes the ContainerConfiguration as JSON and sends it via XPCMessage with the containerCreate route, ensuring proper sandboxing and privilege separation during container instantiation.

How does Apple Container handle OCI bundle creation?

The server-side ContainersService.swift receives the creation request and delegates bundle materialization to Sources/ContainerBuild/Builder.swift. This component writes the rootfs, generates the config.json specification, and sets up the required filesystem mounts and cgroups before handing execution to the underlying OCI runtime.

Can I create containers programmatically without using the CLI?

Yes. The ContainerClient class in Sources/Services/ContainerAPIService/Client/ContainerClient.swift exposes a Swift API that accepts ContainerConfiguration, ContainerCreateOptions, and Kernel objects. Calling client.create() constructs the appropriate XPC message and communicates directly with the daemon, bypassing the command-line interface entirely.

Where is the container configuration validated before runtime?

Initial validation occurs in Sources/ContainerCommands/Container/Utility.swift through containerConfigFromFlags(), which translates CLI arguments into strongly-typed structures. Additional server-side validation occurs in ContainersService.swift when the daemon decodes the XPC payload and verifies the configuration before invoking the OCI runtime to prevent invalid container states.

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 →