# How Apple’s Container Runtime Manages Container Lifecycle: XPC Service Architecture

> Discover how Apple's container runtime manages container lifecycle using its XPC service architecture. Learn about its bootstrap, execution, monitoring, and termination processes.

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

---

**Apple’s container runtime manages container lifecycle through a privileged XPC service (`RuntimeService`) that exposes distinct routes for bootstrap, process execution, monitoring, and termination, coordinated by an internal state machine and accessed via a Swift client façade (`RuntimeClient`).**

The `apple/container` repository implements a Linux-compatible container runtime that leverages macOS XPC (Inter-Process Communication) to isolate privileged operations from user-facing applications. Understanding how this runtime manages container lifecycle reveals an architecture where a stateful service coordinates VM instantiation, process execution, and resource teardown through nine distinct phases.

## Architecture Overview

At the core of Apple’s container runtime is a **request-response XPC service** pattern. The `RuntimeService` runs in a privileged process and owns a single VM-backed container, exposing routes that correspond to each lifecycle phase. The `RuntimeClient` serves as a façade that builds XPC messages, sends them to the service, and interprets replies.

All lifecycle transitions are guarded by an internal **state machine** (`State` enum) inside `RuntimeService`. According to the source code in [`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift), state mutations occur exclusively inside an `AsyncLock` to serialize concurrent XPC calls, preventing race conditions between bootstrap, stop, and kill operations.

## Lifecycle Phases

The container runtime progresses through nine distinct phases, from endpoint creation to final shutdown.

### 1. Endpoint Creation and Bootstrap

The lifecycle begins when `RuntimeService.createEndpoint` transforms an anonymous XPC connection into a persistent endpoint. Subsequently, `RuntimeService.bootstrap` (implemented in [`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift) lines 31-102) instantiates the VM, attaches the rootfs, allocates network interfaces, and prepares the init process. If the container bundle does not exist, the bootstrap sequence creates it automatically.

### 2. Process Execution

Once bootstrapped, the init process (PID 1) launches via `RuntimeService.startProcess`, transitioning the internal state to `.running`. For additional processes (equivalent to `docker exec`), the runtime uses `RuntimeService.createProcess`, which registers the process with the exit monitor and stores its `ProcessInfo`. These operations are defined in [`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift) lines 13-55.

### 3. Monitoring and Observability

Clients retrieve container snapshots through `RuntimeService.state`, which returns the current status (running, stopped, booted, etc.) and network topology. Runtime metrics collection occurs via `RuntimeService.statistics`, gathering memory, CPU, network I/O, block I/O, and process count from the VM and returning them as JSON. These routes are implemented in lines 66-99 and 36-70 of [`RuntimeService.swift`](https://github.com/apple/container/blob/main/RuntimeService.swift), respectively.

### 4. Termination and Shutdown

The termination phase supports both graceful and forceful paths. `RuntimeService.stop` sends a termination signal to the init process, waits for graceful shutdown, then tears down the VM, network sessions, and socket forwarders. For immediate termination, `RuntimeService.kill` signals individual processes directly; when issuing `SIGKILL`, the client blocks until the exit is observed. Finally, `RuntimeService.shutdown` transitions the service to `.shuttingDown` and releases all resources, allowing subsequent re-bootstrapping.

## Client-Side Implementation

The `RuntimeClient` mirrors the XPC routes with Swift methods, encoding payloads such as `ProcessConfiguration` and dynamic environment variables. Errors are uniformly wrapped in `ContainerizationError`.

```swift
import ContainerRuntimeClient

// Create a client for a specific container instance
let runtimeClient = try await RuntimeClient.create(
    id: "myContainer",
    runtime: "linux"
)

// Bootstrap the container (VM creation, networking, init process)
try await runtimeClient.bootstrap(
    stdio: [stdinHandle, stdoutHandle, stderrHandle],
    networkBootstrapInfos: networkInfos,
    dynamicEnv: ["LANG": "en_US.UTF-8"]
)

// Start the init process (PID 1)
try await runtimeClient.startProcess("myContainer")

// Launch an additional process (equivalent to container exec)
let execConfig = ProcessConfiguration(
    args: ["ls", "-la"],
    env: ["PATH": "/usr/bin"],
    cwd: "/"
)
try await runtimeClient.createProcess(
    "exec-1",
    config: execConfig,
    stdio: [nil, stdoutHandle, stderrHandle]
)
try await runtimeClient.startProcess("exec-1")

// Query container state
let snapshot = try await runtimeClient.state()
print("Container is \(snapshot.status)")

// Retrieve runtime statistics
let stats = try await runtimeClient.statistics()
print("CPU µs: \(stats.cpuUsageUsec ?? 0)")

// Gracefully stop the container
let stopOptions = ContainerStopOptions(signal: "SIGTERM", timeoutInSeconds: 30)
try await runtimeClient.stop(options: stopOptions)

// Force-kill a specific process
try await runtimeClient.kill("exec-1", signal: "SIGKILL")

// Shut down the service
try await runtimeClient.shutdown()

```

## Key Source Files

The lifecycle implementation spans several critical files in the repository:

- **[`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift)**: Core XPC service implementing all lifecycle routes, state machine, and `AsyncLock` serialization.

- **[`Sources/Services/Runtime/RuntimeClient/RuntimeClient.swift`](https://github.com/apple/container/blob/main/Sources/Services/Runtime/RuntimeClient/RuntimeClient.swift)**: High-level client façade that builds XPC messages for each lifecycle operation.

- **[`Sources/Services/Runtime/RuntimeClient/RuntimeRoutes.swift`](https://github.com/apple/container/blob/main/Sources/Services/Runtime/RuntimeClient/RuntimeRoutes.swift)**: Enum defining XPC route names including `bootstrap`, `createProcess`, and `stop`.

- **[`Sources/Services/Runtime/RuntimeClient/RuntimeKeys.swift`](https://github.com/apple/container/blob/main/Sources/Services/Runtime/RuntimeClient/RuntimeKeys.swift)**: Encoding/decoding keys for XPC payloads such as `runtimeServiceEndpoint` and `dynamicEnv`.

- **[`Sources/Services/Runtime/RuntimeClient/RuntimeConfiguration.swift`](https://github.com/apple/container/blob/main/Sources/Services/Runtime/RuntimeClient/RuntimeConfiguration.swift)**: Codable runtime configuration persisted in the container bundle.

- **[`Sources/ContainerPersistence/ConfigurationLoader.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ConfigurationLoader.swift)**: Loads bundle configuration during the bootstrap phase.

- **[`Sources/ContainerPersistence/ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ContainerSystemConfig.swift)**: Persists system-wide configuration including network and DNS settings.

## Summary

- **Apple’s container runtime** isolates privileged operations in `RuntimeService`, an XPC service that manages a single VM-backed container through a strict state machine.
- **Lifecycle management** follows nine phases: endpoint creation, bootstrap, init process start, exec process creation, state queries, statistics collection, graceful stop, force kill, and shutdown.
- **Concurrency control** relies on `AsyncLock` to serialize state transitions and prevent race conditions during concurrent XPC calls.
- **Client interaction** occurs through `RuntimeClient`, which provides a Swift async/await API wrapping the underlying XPC routes defined in [`RuntimeRoutes.swift`](https://github.com/apple/container/blob/main/RuntimeRoutes.swift).

## Frequently Asked Questions

### What is the difference between stop and kill in Apple's container runtime?

**Stop** (`RuntimeService.stop`) initiates a graceful shutdown by sending a configurable signal (default `SIGTERM`) to the init process, waiting for the specified timeout, then tearing down the VM and network resources. **Kill** (`RuntimeService.kill`) sends a signal directly to a specific process (including the init process) and, when using `SIGKILL`, blocks the client until the exit is observed, providing immediate termination without graceful cleanup.

### How does the runtime handle concurrent lifecycle operations?

The runtime serializes all lifecycle transitions using an `AsyncLock` inside `RuntimeService`. According to the implementation in [`Sources/Services/RuntimeLinux/Server/RuntimeService.swift`](https://github.com/apple/container/blob/main/Sources/Services/RuntimeLinux/Server/RuntimeService.swift), the internal `State` enum mutations occur exclusively within this lock, preventing race conditions when multiple clients simultaneously attempt to bootstrap, stop, or kill containers.

### What is the role of XPC in the container runtime architecture?

XPC (Inter-Process Communication) provides the transport layer between the privileged `RuntimeService` and unprivileged clients. The service exposes routes such as `bootstrap`, `createProcess`, and `statistics` as XPC endpoints, while `RuntimeClient` encodes Swift structures into XPC messages using keys defined in [`RuntimeKeys.swift`](https://github.com/apple/container/blob/main/RuntimeKeys.swift), enabling secure, sandboxed container management.

### How are container statistics collected and formatted?

The runtime collects metrics via `RuntimeService.statistics`, which queries the VM for memory usage, CPU microseconds, network I/O, block I/O, and process counts. These metrics are returned as JSON-encoded data that `RuntimeClient` decodes into Swift structures, allowing applications to monitor resource consumption without direct VM access.