Architecture of the Apple Container Tool: Inside macOS's OCI-Compatible Runtime
The Apple container tool implements a multi-layered Swift architecture where a CLI client communicates via XPC to a privileged daemon, which orchestrates per-container Linux VMs using Apple's Virtualization framework alongside specialized helpers for image operations and vmnet-based networking.
The apple/container repository provides a native container runtime designed specifically for macOS 26 on Apple Silicon. Unlike traditional container implementations, this tool integrates deeply with macOS-native frameworks including Virtualization, vmnet, and XPC services to deliver an OCI-compatible experience through a multi-process design that strictly separates user interfaces from privileged system operations.
CLI and XPC Communication Layer
The user-facing interface relies on XPC (Inter-Process Communication) to bridge the gap between the unprivileged CLI and the system daemon. When you execute commands like container run, the tool constructs an XPCMessage and transmits it to the daemon via the com.apple.container.xpc.route endpoint.
In Sources/ContainerXPC/XPCMessage.swift, the XPCMessage struct encapsulates both the route identifier and payload data. The server implementation in Sources/ContainerXPC/XPCServer.swift maintains a route table mapping string identifiers to async handler closures. A typical container creation route appears as:
let routes: [String: XPCServer.RouteHandler] = [
"container.create": XPCServer.route { message in
// decode request, spin up a VM, return container ID
},
// … other routes …
]
The server validates the client's EUID, dispatches the request to the appropriate handler, and returns either a success message or a ContainerizationError encoded via ContainerXPCError.
Daemon and Service Architecture
The container-apiserver runs as a Launch Daemon, providing the privileged backend that manages container lifecycle operations. This daemon owns the XPC server and coordinates communication between the CLI and various helper processes.
Launchd integration allows the system to start and stop the daemon automatically via container system start/stop commands. The daemon's responsibilities include launching per-container helpers, managing the OCI-compatible content store, and coordinating network configuration through the vmnet framework.
Per-Container Runtime Architecture
For each container, the daemon spawns a container-runtime-linux helper binary that executes inside a lightweight Linux VM. The Containerization Swift package wraps Apple's Virtualization framework to create these isolated execution environments.
Each VM runs a minimal Linux kernel with a user-space init process that forwards container-specific syscalls to the host. The SocketForwarder module, implemented in Sources/SocketForwarder/TCPForwarder.swift and its UDP counterpart, bridges network traffic between the host and container VM:
import SocketForwarder
let forwarder = TCPForwarder(
listenPort: 8080,
destinationHost: "127.0.0.1",
destinationPort: 80
)
try forwarder.start()
print("Forwarding 0.0.0.0:8080 → 127.0.0.1:80")
Image Management and Storage
Image operations are handled by the container-core-images helper, which interacts directly with the OCI content store located at ~/.container. This component manages pull, push, and caching operations while storing registry credentials securely in the macOS Keychain.
The workflow follows this sequence:
- CLI sends an XPC request with route
image.pullto the daemon - The daemon forwards to
container-core-images, which authenticates via Keychain - The helper downloads manifest layers and stores them in the local OCI content store
- An OCI-compatible image reference returns to the daemon for use by the runtime VM
Virtual Networking Stack
Networking leverages the vmnet framework through the container-network-vmnet helper. This component creates virtual network interfaces and assigns IP addresses to each container VM.
On macOS 26, the architecture supports multiple isolated networks, while macOS 15 restricts operations to a single default network. The SocketForwarder facilitates TCP and UDP proxying between the host network stack and the container's virtual interface.
Configuration System
All daemon and helper behavior is driven by a TOML configuration file parsed into Swift structs defined in Sources/ContainerPersistence/ContainerSystemConfig.swift. The configuration hierarchy includes:
BuildConfig– Builder image specifications and resource limitsContainerConfig– Default CPU and memory allocations per containerNetworkConfig– Optional subnet overrides for vmnetKernelConfig– Path and URL for the Linux kernel binaryVminitConfig– Vminit helper image settings
The system provides sensible defaults for all fields, making the tool usable without manual configuration while allowing granular customization through config.toml:
import ContainerPersistence
let configURL = URL(fileURLWithPath: "\(NSHomeDirectory())/.container/config.toml")
let loader = ConfigurationLoader()
let systemConfig = try loader.load(from: configURL)
print("Default container memory: \(systemConfig.container.memory)")
Programmatic XPC Client Usage
Developers can interact with the daemon directly using the same XPC facilities as the CLI:
import ContainerXPC
import Foundation
let client = XPCClient(identifier: "com.apple.container.apiserver")
let request = XPCMessage(route: "container.create")
request.set(key: "image", value: "docker.io/library/nginx:latest")
request.set(key: "command", value: ["/usr/sbin/nginx", "-g", "daemon off;"])
Task {
do {
let response = try await client.send(request)
try response.error()
let containerID = response.string(key: "containerID")
print("Container started: \(containerID ?? "unknown")")
} catch {
print("Failed: \(error)")
}
}
Summary
- The Apple container tool uses a multi-process architecture separating the CLI, daemon, and per-container helpers via XPC communication
- The daemon (
container-apiserver) runs as a Launch Daemon and coordinates image pulls, networking, and VM lifecycle through specialized helpers - Each container runs in a lightweight Linux VM using the
Virtualizationframework, withcontainer-runtime-linuxhandling execution - Image management occurs through
container-core-images, which maintains an OCI-compatible content store at~/.containerand uses Keychain for credentials - Networking is provided by
container-network-vmnetusing the vmnet framework, withSocketForwarderbridging host and container traffic - Configuration is managed through
ContainerSystemConfig.swift, parsingconfig.tomlinto type-safe Swift structs with sensible defaults
Frequently Asked Questions
How does the Apple container tool differ from Docker Desktop?
The Apple container tool integrates directly with macOS-native frameworks like Virtualization, vmnet, and XPC services rather than relying on a traditional Linux VM or virtual machine manager. According to Sources/ContainerXPC/XPCServer.swift, it uses a privileged daemon architecture with per-container helpers, whereas Docker typically uses a single privileged VM running Linux.
What is the purpose of the XPC layer in the container architecture?
The XPC layer provides secure inter-process communication between the unprivileged CLI and the privileged container-apiserver daemon. As implemented in Sources/ContainerXPC/XPCMessage.swift, it validates client EUIDs and routes requests through a structured message protocol, ensuring that privileged operations like VM creation and network configuration are properly authorized.
Where does the tool store container images and configuration?
The tool maintains an OCI-compatible content store under ~/.container for image layers and manifests, as handled by container-core-images. System-wide configuration is parsed from config.toml into ContainerSystemConfig structs defined in Sources/ContainerPersistence/ContainerSystemConfig.swift, while registry credentials are stored securely in the macOS Keychain.
Can I run the Apple container tool on macOS versions earlier than 26?
While the tool may function on earlier versions, the architecture specifically supports multiple isolated networks on macOS 26, whereas macOS 15 restricts networking to a single default network. The Virtualization framework requirements and daemon architecture are optimized for macOS 26 and Apple Silicon hardware.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →