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:

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:

Summary

  • The ContainerAPIClient module provides the primary interface for programmatic container management in apple/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 ContainerConfiguration ensure 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:

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 →