# Apple Container Lifecycle Management: How the System Orchestrates Create, Run, and Delete Operations

> Understand Apple Container lifecycle management. Learn how it orchestrates container create run and delete operations with its dedicated API server and XPC helpers.

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

---

**Apple Container implements a full-cycle management model that orchestrates container creation, bootstrap, execution, and deletion through a dedicated `container-apiserver` and XPC-based helper processes.**

Apple Container is Apple's open-source container runtime that manages Linux containers on macOS using lightweight virtualization. The lifecycle management for containers follows a distinct create-bootstrap-run-stop-delete flow coordinated by the `container-apiserver` launch agent and its XPC helpers. This architecture ensures that each container operates in isolation while sharing system resources efficiently.

## The Eight-Phase Container Lifecycle

Apple Container divides container management into eight distinct phases, from system initialization to resource teardown. Each phase is handled asynchronously via Swift's `async/await` and isolated in separate processes to prevent failures from cascading between containers.

### Phase 1: System Start

Before any container runs, the system must initialize its infrastructure. The `container system start` command launches the `container-apiserver` launch agent, which spawns three critical XPC helpers:

- **`container-core-images`** – Manages the image store and OCI image pulling
- **`container-network-vmnet`** – Handles virtual network configuration via the vmnet framework
- **`container-runtime-linux`** – One instance per container that creates lightweight Linux VMs

This initialization is documented in [`docs/technical-overview.md`](https://github.com/apple/container/blob/main/docs/technical-overview.md) and implemented in `Sources/APIServer/APIServer+Start.swift`.

### Phase 2: Create

When you execute `container run`, the **ContainerCLI** ([`Sources/CLI/ContainerCLI.swift`](https://github.com/apple/container/blob/main/Sources/CLI/ContainerCLI.swift)) parses arguments and delegates to `ContainerRun.run` in [`Sources/ContainerCommands/Container/ContainerRun.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerRun.swift). This method:

1. Generates a unique container ID via `Utility.createContainerID(name:)`
2. Builds a **container configuration** from command-line flags (image, resources, network)
3. Calls `client.create(configuration:options:)` to register the container with the apiserver

The container exists in a configured but not yet running state after this phase.

### Phase 3: Bootstrap (Start)

After creation, the CLI immediately bootstraps the container. In `ContainerRun.run`, the code opens a **stdio conduit** using `ProcessIO.create` and calls:

```swift
let process = try await client.bootstrap(
    id: id, 
    stdio: io.stdio, 
    dynamicEnv: [:]
)

```

This XPC request asks the `container-runtime-linux` helper to start the init process inside a lightweight VM that backs the container. The helper returns a `ContainerProcess` object representing the running init process.

### Phase 4: Run and Attach

The runtime behavior depends on the execution mode:

- **Detached mode** (`--detach`): The CLI calls `process.start()` asynchronously, prints the container ID, and exits. The container continues running in the background.
- **Interactive mode**: The CLI forwards stdin/stdout/stderr to the init process via `io.handleProcess`, blocking until the process exits.

This logic appears in [`ContainerRun.swift`](https://github.com/apple/container/blob/main/ContainerRun.swift) lines 54-62, where the code branches based on the detach flag.

### Phase 5: Stop and Signal Handling

The runtime monitors Unix signals through a **SignalThreshold** mechanism. If the process receives three `SIGINT` or `SIGTERM` events, the system forces a hard exit. When the init process exits naturally, `ContainerRun` captures the exit code and returns it as the CLI exit status.

Signal handling occurs in the same `ContainerRun.run` method, ensuring that foreground containers properly propagate exit codes to the shell.

### Phase 6: Cleanup on Failure

If any lifecycle step throws an exception, the CLI executes error-path cleanup. The `ContainerRun.run` method includes a `defer` or catch block that calls:

```swift
client.delete(id: containerId)

```

This ensures the system does not retain stale entries for partially-created containers, preventing resource leaks and ID conflicts.

### Phase 7: Delete (Explicit)

When you run `container rm <id>`, the CLI invokes `client.delete(id:)` from the **ContainerClient** module. This RPC triggers the apiserver to:

1. Tear down the lightweight VM
2. Remove the sandbox directory
3. Free the container ID in the persistence layer

The deletion is handled by the `ContainerClient.delete` implementation in the `ContainerAPIClient` package.

### Phase 8: System Stop

The `container system stop` command terminates the entire ecosystem. The apiserver shuts down all helper processes (image store, network daemon, and per-container runtimes), releasing every VM resource. This gracefully concludes the lifecycle management cycle.

## Architecture Components Supporting Lifecycle Management

Several key components collaborate to manage container lifecycles:

**`ContainerCLI`** ([`Sources/CLI/ContainerCLI.swift`](https://github.com/apple/container/blob/main/Sources/CLI/ContainerCLI.swift))
The entry point that parses command-line arguments and forwards them to the Application framework.

**`Application`**
A thin wrapper around the container-system configuration and the XPC client that coordinates between CLI commands and the apiserver.

**`ContainerClient`** (`ContainerAPIClient` package)
Sends asynchronous RPCs (`create`, `bootstrap`, `delete`, `stop`) over XPC to the apiserver, abstracting the inter-process communication complexity.

**`container-apiserver`**
A launch-agent ([`Sources/APIServer/APIServer.swift`](https://github.com/apple/container/blob/main/Sources/APIServer/APIServer.swift)) that owns the in-process state (`ContainerSystemConfig`) and marshals requests to the appropriate XPC helpers.

**`container-runtime-linux`**
Per-container XPC helper that creates lightweight Linux VMs, starts init processes, and forwards I/O between the host and guest.

**`container-core-images`** and **`container-network-vmnet`**
Shared XPC helpers managing the image store and virtual network interface, respectively.

**`ContainerPersistence`**
Maintains on-disk JSON/YAML representations of containers, networks, and system configuration (e.g., [`Sources/ContainerPersistence/ContainerSystemConfig.swift`](https://github.com/apple/container/blob/main/Sources/ContainerPersistence/ContainerSystemConfig.swift)).

## Practical Lifecycle Commands

Below are the shell commands that trigger each lifecycle phase, with their underlying Swift implementations.

### Start the Container System

```bash
container system start

```

*Spawns the apiserver and XPC helpers as described in the technical overview.*

### Create and Run a Container

```bash

# Detached execution

container run --detach --name myapp ghcr.io/example/app:latest

# Interactive execution

container run -it --name myapp ghcr.io/example/app:latest /bin/bash

```

*Underlying implementation:*

```swift
let id = Utility.createContainerID(name: "myapp")
try await client.create(configuration: cfg, options: .init(autoRemove: false))
let process = try await client.bootstrap(id: id, stdio: io.stdio, dynamicEnv: [:])
try await process.start()

```

### Execute Commands in Running Containers

```bash
container exec -ti myapp /bin/bash

```

*Opens a new process inside the existing VM using the same XPC client connection.*

### Stop and Remove Containers

```bash
container stop myapp    # Sends graceful stop signal

container rm myapp      # Removes configuration and VM resources

```

*Translates to:*

```swift
try await client.stop(id: "myapp")
try await client.delete(id: "myapp")

```

### Shut Down the System

```bash
container system stop

```

*Terminates the apiserver and all XPC helpers, releasing every lightweight VM.*

## Summary

- Apple Container implements an **eight-phase lifecycle** covering system start, create, bootstrap, run, stop, cleanup, delete, and system stop.
- The **`container-apiserver`** launch agent coordinates all lifecycle operations through XPC helpers for images, networking, and runtime.
- **Container IDs** are generated via `Utility.createContainerID` and managed persistently through `ContainerSystemConfig`.
- Lifecycle operations are **asynchronous** (using Swift `async/await`) and **process-isolated**, ensuring failures don't cascade between containers.
- The **`ContainerRun.run`** method in [`Sources/ContainerCommands/Container/ContainerRun.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerRun.swift) implements the core create-bootstrap-run logic with automatic cleanup on failure.

## Frequently Asked Questions

### How does Apple Container generate unique container IDs?

Apple Container generates unique identifiers through the `Utility.createContainerID(name:)` function, which creates a unique string based on the provided name and internal entropy. This ID is used throughout the lifecycle in [`Sources/ContainerCommands/Container/ContainerRun.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerRun.swift) and persists in the `ContainerPersistence` layer until explicitly deleted.

### What happens if a container fails to start during the bootstrap phase?

If the bootstrap phase fails or any preceding step throws an exception, the CLI executes automatic cleanup by calling `client.delete(id:)` to remove the partially-created container. This prevents the system from retaining stale entries and ensures that failed containers don't consume resources or block future ID allocations.

### How does Apple Container handle process signals during shutdown?

The runtime implements a **SignalThreshold** monitor that tracks `SIGINT` and `SIGTERM` events. After receiving three consecutive signals, the system forces a hard exit of the container process. For graceful shutdowns, the runtime captures the init process exit code and propagates it to the CLI, ensuring proper shell exit status reporting.

### What is the difference between container stop and container delete?

**Stop** (`container stop`) sends a signal to the running init process via the runtime helper, gracefully terminating the container process while preserving the container configuration and VM state for potential restart. **Delete** (`container rm`) invokes the apiserver's delete RPC, which tears down the VM, removes the sandbox directory, and frees the container ID from persistence, completely removing all traces of the container from the system.