Apple Container XPC Services Architecture: How apiserver and core-images Communicate

Apple's container daemon uses dedicated XPC helper processes that run as separate Mach services to isolate image management, networking, and runtime functions from the main apiserver.

The apple/container repository implements a modular container runtime for macOS that leverages macOS XPC (cross-process communication) to sandbox privileged operations. Instead of monolithic architecture, the system splits functionality into separate XPC services that communicate via Mach messages, providing crash isolation and clear security boundaries.

What Are XPC Services in Apple Container?

XPC services in Apple Container are separate binary executables launched by container-apiserver through launchd. Each helper runs under its own service identifier and registers a Mach service that listens for incoming requests. The main apiserver acts as a client to these services, routing container operations like image pulls and network configuration to the appropriate helper.

This design ensures that a failure in the image store does not crash the entire daemon, and allows the system to restart individual helpers on demand.

The Three Core XPC Helpers

The architecture defines three primary XPC helpers, each registered with a specific com.apple.container.core service identifier:

  • container-core-images – Provides the OCI-image management API (list, pull, push, tag, unpack) and owns the local content store. Service identifier: com.apple.container.core.container-core-images.
  • container-network-vmnet – Exposes the virtual-network API built on the vmnet framework. Service identifier: com.apple.container.core.container-network-vmnet.
  • container-runtime-linux – Spawns one instance per container to manage VM-backed container lifecycle, I/O, and networking. Service identifier: com.apple.container.core.container-runtime-linux.

How XPC Communication Works

The XPC stack is organized into three distinct layers that handle transport, routing, and business logic.

Transport Layer

The ContainerXPC library provides thin wrappers around the native XPC C API. The XPCServer class in Sources/ContainerXPC/XPCServer.swift sets up the Mach listener, validates the caller's UID, and dispatches incoming messages to registered handlers. On the client side, XPCClient in Sources/ContainerXPC/XPCClient.swift creates xpc_connection_t objects, sends XPCMessage dictionaries, and manages the 60-second registration timeout (XPCClient.xpcRegistrationTimeout).

Routing

Each helper defines an enum of routes (e.g., ImagesServiceXPCRoute in Sources/Services/ContainerImagesService/Client/ImageServiceXPCRoutes.swift) that map string identifiers to Swift async handlers. The server maintains a route table that associates these strings with methods on the service harness.

Service Logic

The actual business logic lives in "service" actors (e.g., ImagesService) and "harnesses" (e.g., ImagesServiceHarness). The harness translates XPC requests into method calls on the actor, keeping the transport layer decoupled from the implementation details.

Core-Images Service Architecture

The container-core-images helper demonstrates the complete XPC flow from launch to request handling.

Helper Startup

When container system start runs, the apiserver launches the helper via launchctl. The ImagesHelper.Start.run() method in Sources/Plugins/CoreImages/ImagesHelper.swift initializes the service:

let routes = try self.initializeImagesService(
    root: appRoot,
    containerSystemConfig: containerSystemConfig,
    log: log,
    routes: &routes)

let xpc = XPCServer(
    identifier: "com.apple.container.core.container-core-images",
    routes: routes,
    log: log)

try await xpc.listen()

The initializeImagesService function binds each route in ImagesServiceXPCRoute to a method on ImagesServiceHarness.

Client Requests

The container client library (ContainerImagesServiceClient) creates an XPCClient with the service identifier and builds request messages. For example, pulling an image:

import ContainerImagesServiceClient
import ContainerXPC

let client = XPCClient(service: "com.apple.container.core.container-core-images")
let request = XPCMessage(route: ImagesServiceXPCRoute.imagePull)
request.set(string: "ubuntu:latest", forKey: "reference")

let response = try await client.send(request)
let imageDesc = try response.decode(ImageDescription.self)

This pattern is used by the CLI when executing container image pull.

Message Handling

When XPCServer.handleMessage receives the request, it verifies the caller, extracts the route string, and dispatches to the corresponding harness method. The harness calls into the ImagesService actor to perform the OCI operation, then serializes the result back into an XPCMessage. Errors are wrapped in ContainerizationError and returned to the client.

Code Implementation Examples

Starting the XPC Server

The helper executable registers routes and begins listening:

// In Sources/Plugins/CoreImages/ImagesHelper.swift
func run() async throws {
    var routes: [String: XPCServer.RouteHandler] = [:]
    
    // Initialize service and populate routes
    try initializeImagesService(
        root: appRoot,
        containerSystemConfig: config,
        log: log,
        routes: &routes
    )
    
    let server = XPCServer(
        identifier: serviceIdentifier,
        routes: routes,
        log: log
    )
    
    try await server.listen()
}

Adding a New Route

To extend the API, define the route and handler:

// 1. Define the route in ImageServiceXPCRoutes.swift
enum ImagesServiceXPCRoute: String {
    case imagePull
    case imagePush
    case myNewOperation
}

// 2. Implement handler in ImagesServiceHarness.swift
extension ImagesServiceHarness {
    func myNewOperation(_ message: XPCMessage, _ session: XPCServerSession) async throws -> XPCMessage {
        let param = try message.getString(forKey: "parameter")
        let result = try await service.performOperation(param)
        
        var reply = message.reply()
        reply.set(string: result, forKey: "result")
        return reply
    }
}

// 3. Register in initializeImagesService
routes[ImagesServiceXPCRoute.myNewOperation.rawValue] = XPCServer.route(harness.myNewOperation)

Summary

  • Separate Process Architecture: Major subsystems run as isolated XPC helpers (container-core-images, container-network-vmnet, container-runtime-linux) with unique Mach service identifiers.
  • Three-Layer Stack: Transport (XPCServer/XPCClient), Routing (ImagesServiceXPCRoute enums), and Service Logic (ImagesService actors with harness wrappers).
  • Crash Isolation: Each helper runs in its own sandbox; failures do not bring down the main container-apiserver process.
  • Automatic Recovery: The client implementation retries connections after a 60-second registration timeout, allowing helpers to restart on demand.
  • Clear Boundaries: The ContainerXPC library abstracts the native XPC C API, while service-specific code in Sources/Services/ handles business logic.

Frequently Asked Questions

What is the XPC service identifier for the core-images helper?

The identifier is com.apple.container.core.container-core-images. This Mach service name is registered with launchd when the helper starts, and clients must use this exact string when creating an XPCClient connection to send image management requests.

How does the architecture provide crash isolation?

Each XPC helper runs as a separate process with its own address space. If container-core-images crashes while handling a corrupted image layer, the container-apiserver process remains unaffected and can restart the helper via launchctl. The client library automatically retries connections after the 60-second registration timeout expires.

Where are the XPC routes defined and registered?

Routes are defined as string enums in Sources/Services/ContainerImagesService/Client/ImageServiceXPCRoutes.swift. They are registered with the server in Sources/Plugins/CoreImages/ImagesHelper.swift inside the initializeImagesService function, which populates a route table mapping each route string to a handler method on ImagesServiceHarness.

What files implement the core XPC transport layer?

The transport layer is implemented in Sources/ContainerXPC/XPCServer.swift (server-side Mach listener and dispatch), Sources/ContainerXPC/XPCClient.swift (client-side connection management), and Sources/ContainerXPC/XPCMessage.swift (message encoding/decoding). These files provide the Swift wrappers around the native XPC C API used by all helpers.

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 →