How to Use the Client API for Programmatic Container Management in Apple Container
The Apple Container project exposes a Swift-based client API through the ContainerAPIClient module that enables fully asynchronous container lifecycle management via XPC communication with the container daemon.
The apple/container repository provides a native macOS container runtime that you can control programmatically from your own Swift applications. By importing the ContainerAPIClient module, you gain access to type-safe methods for creating, managing, and monitoring containers without invoking command-line tools. This interface abstracts the underlying XPC transport into modern Swift concurrency patterns, making it straightforward to integrate container operations into your app's architecture.
Architecture of the Client API
The programmatic interface is organized into three distinct layers that handle communication, command mapping, and data modeling.
XPC Transport Layer
At the foundation, the XPC Transport manages inter-process communication between your client application and the container daemon. The XPCClient class initializes connections, while XPCClientSession maintains the lifecycle of individual request-response cycles. These components serialize Swift structs into XPC messages and deserialize daemon responses back into Swift types.
Key source files:
Service Stubs and High-Level Interface
The ContainerAPIClient target provides the service stubs that expose high-level async methods mapping one-to-one to daemon RPC endpoints. Rather than manually constructing XPC messages, you call Swift methods like pullImage(named:) or createContainer(with:), which return Result types or throw errors that propagate daemon-side failures.
This layer handles all containers, images, networks, volumes, machines, and system commands through a consistent async/await interface.
Domain Models and Configuration
Domain models define the data structures exchanged with the daemon. Types such as ContainerConfiguration, ImageReference, and NetworkAttachment conform to Codable, enabling automatic serialization by the XPC layer. These models mirror the configuration options available in the CLI but provide compile-time safety and IDE autocomplete support.
Key source files:
Sources/ContainerPersistence/ContainerSystemConfig.swiftSources/ContainerCommands/Container/ContainerCreate.swift
Setting Up the Client API
To begin using the client API for programmatic container management, import the required modules and instantiate an XPCClient. The default client connects to the system daemon automatically, requiring no manual configuration for standard deployments.
import ContainerAPIClient
import ContainerXPC
// Initialize the default client connection
let client = XPCClient()
All operations are asynchronous and use Swift's structured concurrency, allowing you to integrate them seamlessly into Task groups or async function chains.
Programmatic Container Management Examples
The following examples demonstrate common container operations using the ContainerAPIClient interface.
Connecting to the Daemon
While XPCClient handles connection establishment automatically, you can verify connectivity by listing system information or checking daemon status before proceeding with resource-intensive operations.
Pulling Images
Download container images programmatically using the pullImage method, which accepts a fully qualified image name and streams progress through the async interface.
Task {
do {
try await client.pullImage(named: "docker.io/library/alpine:latest")
print("✅ Image pulled successfully")
} catch {
print("❌ Pull failed: \(error)")
}
}
Creating and Starting Containers
Construct a ContainerConfiguration instance to define runtime parameters, then pass it to createContainer to receive a unique container identifier. Start the container using the returned ID.
Task {
do {
let config = ContainerConfiguration(
image: "docker.io/library/alpine:latest",
command: ["/bin/sh", "-c", "while true; do echo hello; sleep 1; done"]
)
let containerID = try await client.createContainer(with: config)
try await client.startContainer(id: containerID)
print("🚀 Container started: \(containerID)")
} catch {
print("❌ Could not start container: \(error)")
}
}
Executing Commands in Containers
Run commands inside running containers using the execInContainer method, which captures stdout and stderr streams and returns them as structured data.
Task {
do {
let execResult = try await client.execInContainer(
id: "my-container",
command: ["cat", "/etc/os-release"]
)
print("📄 Exec output:\n\(execResult.stdout)")
} catch {
print("❌ Exec failed: \(error)")
}
}
Listing and Removing Containers
Manage container lifecycle by querying active containers and removing them when no longer needed. The listContainers method returns an array of metadata objects, while removeContainer accepts a force flag to terminate running instances.
Task {
do {
// List all containers
let containers = try await client.listContainers()
containers.forEach { print("\($0.id) – \($0.status)") }
// Remove a specific container
try await client.removeContainer(id: "my-container", force: true)
print("🗑️ Container removed")
} catch {
print("❌ Operation failed: \(error)")
}
}
Key Source Files and References
The following files define the client API surface and provide implementation details for extending or debugging the programmatic interface:
Sources/ContainerXPC/XPCClient.swift– Low-level XPC connection management and message serialization.Sources/ContainerXPC/XPCClientSession.swift– Session lifecycle handling and request multiplexing.Sources/ContainerCommands/Container/ContainerCreate.swift–ContainerConfigurationdefinitions and creation logic.Sources/ContainerCommands/Image/ImagePull.swift– Image pulling and registry interaction methods.Sources/ContainerCommands/Network/NetworkCreate.swift– Network provisioning and attachment APIs.Sources/ContainerCommands/Volume/VolumeCreate.swift– Persistent volume management operations.Sources/ContainerCommands/Machine/MachineCreate.swift– VM-based container machine controls.Sources/ContainerCommands/System/SystemStart.swift– Daemon lifecycle and system status queries.Sources/ContainerPersistence/ContainerSystemConfig.swift– On-disk configuration persistence models.
Summary
- The
ContainerAPIClientmodule provides the primary interface for programmatic container management inapple/container. - XPCClient and XPCClientSession handle transparent communication with the daemon via
Sources/ContainerXPC/. - All operations are async/await-based, returning specific Swift errors rather than exit codes.
- Domain models like
ContainerConfigurationensure type-safe parameter passing for container, image, network, and volume operations. - You can perform complete lifecycle management—including pull, create, start, exec, list, and remove—without shelling out to command-line tools.
Frequently Asked Questions
How do I handle errors from the container daemon in the client API?
The client API propagates daemon-side failures as Swift Error types that you can catch using standard do-catch blocks. Each method is marked throws and returns detailed error information from the underlying XPC layer, allowing you to distinguish between network failures, image not found errors, and permission issues programmatically.
Can I use the client API from a command-line tool or is it limited to GUI applications?
The ContainerAPIClient is available to any Swift process that can import the module, including command-line tools, daemons, and GUI applications. The XPC transport automatically manages the connection to the system-wide container daemon, regardless of the client process type, though sandboxed apps may require specific entitlements to communicate with the service.
What is the difference between XPCClient and ContainerAPIClient?
XPCClient in Sources/ContainerXPC/XPCClient.swift provides the low-level transport mechanism for sending raw XPC messages, while ContainerAPIClient represents the high-level module containing type-safe wrappers. For programmatic container management, you typically use the high-level methods which internally instantiate and manage XPCClient sessions, rather than interacting with XPC directly.
Is the client API thread-safe for concurrent container operations?
Yes, the client API supports concurrent operations through Swift's structured concurrency. You can launch multiple Task instances to pull images, start containers, and stream logs simultaneously. The underlying XPCClientSession manages request serialization and multiplexing, ensuring thread-safe access to the daemon from multiple concurrent execution contexts.
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 →