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

> Understand Apple's container XPC services architecture. Learn how apiserver and core-images communicate via isolated Mach helper processes for secure image, network, and runtime functions.

- Repository: [Apple/container](https://github.com/apple/container)
- Tags: architecture
- Published: 2026-07-10

---

**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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/Sources/Plugins/CoreImages/ImagesHelper.swift) initializes the service:

```swift
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:

```swift
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:

```swift
// 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:

```swift
// 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`](https://github.com/apple/container/blob/main/Sources/Services/ContainerImagesService/Client/ImageServiceXPCRoutes.swift). They are registered with the server in [`Sources/Plugins/CoreImages/ImagesHelper.swift`](https://github.com/apple/container/blob/main/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`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCServer.swift) (server-side Mach listener and dispatch), [`Sources/ContainerXPC/XPCClient.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCClient.swift) (client-side connection management), and [`Sources/ContainerXPC/XPCMessage.swift`](https://github.com/apple/container/blob/main/Sources/ContainerXPC/XPCMessage.swift) (message encoding/decoding). These files provide the Swift wrappers around the native XPC C API used by all helpers.