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.swiftCLI command builds configurations viaUtility.swiftbefore invokingContainerClient.create(). - XPC messages use the
containerCreateroute to communicate with the privileged daemon atcom.apple.container.apiserver. - The server implementation in
ContainersService.swiftdelegates toBuilder.swiftfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →