Architecture of the Machine API Server in apple/container: Plugin-Based XPC Design

The Machine API Server in apple/container is implemented as a standalone plugin process that exposes an XPC-based RPC interface, comprising four core components—MachineAPIServer, MachinesService, MachinesHarness, and persistent MachineBundles—to manage container-based virtual machines.

The apple/container repository provides a container runtime with virtual machine capabilities. The Machine API Server serves as the central nervous system for managing container-based VMs, exposing a robust RPC interface that decouples the CLI from the underlying container runtime. This architecture leverages Swift’s actor model and XPC communication to provide safe, concurrent access to machine lifecycle operations.

Core Architecture Components

The Machine API Server architecture consists of four tightly integrated components that handle everything from command-line parsing to persistent state management.

MachineAPIServer Entry Point

The MachineAPIServer struct in Sources/Plugins/MachineAPIServer/MachineAPIServer.swift declares the top-level command (machine-apiserver) and wires the Start sub-command. This plugin entry point integrates with the container framework’s command structure, allowing the server to be installed alongside other container plugins.

Start Command and Initialization

The MachineAPIServer.Start implementation in Sources/Plugins/MachineAPIServer/MachineAPIServer+Start.swift handles the bootstrap sequence. The run() method parses CLI flags, creates a structured logger, and constructs a MachinesService instance using two critical paths: appRoot (the plugin state directory) and resourceRoot (supplied by the CLI). It then launches the XPC server, registering routes that map RPC methods to the service layer.

MachinesService Actor

The MachinesService actor in Sources/Services/MachineAPIService/Server/MachinesService.swift forms the heart of the API. This Swift actor owns persistent machine bundles, coordinates concurrent access via an AsyncLock, and communicates with the container runtime through ContainerClient. It tracks running containers through an ExitMonitor and implements the full lifecycle: list, create, delete, boot, stop, inspect, setConfig, and default-machine handling.

The service maintains an in-memory dictionary machines: [String: MachineState] loaded from persistent bundles at startup. All mutating operations execute inside lock.withLock { … } blocks to serialize access and guarantee consistency during concurrent XPC requests.

MachinesHarness XPC Adapter

The MachinesHarness in Sources/Services/MachineAPIService/Server/MachinesHarness.swift serves as a thin XPC façade that unmarshals XPCMessage objects, converts them to Swift types, forwards calls to MachinesService, and encodes JSON replies. This adapter decouples the transport layer from business logic, allowing the service to remain pure Swift while handling XPC-specific serialization concerns.

Data Flow and Request Lifecycle

Understanding the request path clarifies how components interact during machine operations.

  1. Startup: When launched via machine-apiserver start, the Start.run() method initializes MachinesService and registers XPC routes on the identifier MachineClient.serviceIdentifier.

  2. XPC Routing: The server listens for commands like listMachine, createMachine, and bootMachine. Each route points to a corresponding method on MachinesHarness.

  3. Request Handling: MachinesHarness extracts JSON-encoded payloads (such as MachineConfiguration or MachineResources), invokes the matching MachinesService method, and returns JSON or file descriptors.

  4. Machine State: The service loads machine bundles from <appRoot>/machines directories. Each bundle contains the image filesystem, configuration files, and log files.

  5. Container Interaction:

    • create prepares a MachineBundle, copies the rootfs from the image, and stores a snapshot with status stopped.
    • boot converts MachineConfiguration to ContainerConfiguration via MachineConfiguration.toContainerConfig, then asks ContainerClient to create and start the container. It spawns background tasks piping stdout/stderr to log files and registers an ExitMonitor callback.
    • stop forwards the request to ContainerClient and updates the snapshot.
    • inspect merges bundle metadata (disk size, initialization flag) with live container info (IP address).
  6. Concurrency Control: All mutating operations run inside lock.withLock { … } to serialize access, ensuring consistency even when multiple XPC clients issue concurrent commands.

  7. Default Machine: The service stores the default machine name in state.json. Calls to getDefault, setDefault, and parameterless boot reference this value.

Persistence and State Management

The architecture employs a dual-layer state strategy combining in-memory actors with filesystem persistence.

Machine Bundles: Persistent storage lives under <pluginStateRoot>/machines. Each machine bundle contains the rootfs, stdioLog, bootLog, and metadata. The MachineBundle class in Sources/ContainerPersistence/MachineBundle.swift manages this on-disk representation.

Exit Monitoring: The ExitMonitor registers per-machine closures that execute when backing containers exit. This ensures snapshots reset to stopped, log pipelines terminate, and resources cleanup occurs automatically.

Default Machine Tracking: A simple JSON file (state.json) in the plugin directory tracks the default machine name, enabling commands like container-machine boot without explicit --name flags.

CLI Integration and Usage Examples

The container-machine CLI commands communicate with this XPC server transparently. Below are typical usage patterns that exercise the API:


# Start the Machine API helper (normally launched automatically by the daemon)

container-machine-apiserver start --resources /usr/local/share/container/resources

# Create a new machine named "dev-vm" from the alpine image

container-machine create --name dev-vm alpine:latest

# List all machines (shows ID, status, IP, etc.)

container-machine list

# Boot the default machine (or specify an ID)

container-machine boot --name dev-vm

# Inspect a running machine

container-machine inspect dev-vm

# Stop a running machine

container-machine stop dev-vm

# Delete a stopped machine

container-machine delete dev-vm

# Set a machine as the default (used when no --name is supplied)

container-machine set-default dev-vm

For direct API access from Swift (e.g., from another plugin), use the XPC client:

import MachineAPIClient
import ContainerXPC

let client = MachineClient()
let listMessage = XPCMessage(command: MachineRoutes.listMachine.rawValue)
let reply = try await client.send(listMessage)
let machinesData = reply.data(key: MachineKeys.machines.rawValue)!
let machines = try JSONDecoder().decode([MachineSnapshot].self, from: machinesData)
print("Machines:", machines)

Summary

  • The Machine API Server runs as a plugin process with a standalone XPC interface, enabling decoupled communication between CLI tools and the container runtime.
  • Four core componentsMachineAPIServer, Start, MachinesService, and MachinesHarness—handle command parsing, initialization, business logic, and RPC adaptation respectively.
  • Swift actors and AsyncLock provide thread-safe concurrent access to machine state, while ExitMonitor ensures proper cleanup when containers terminate.
  • Machine bundles persist under <appRoot>/machines, storing filesystems, logs, and metadata, with state.json tracking the default machine.
  • The architecture supports both CLI interaction via container-machine commands and programmatic access through MachineClient XPC messages.

Frequently Asked Questions

What is the role of the MachinesService actor in the Machine API Server?

The MachinesService actor is the core business logic layer that manages the complete lifecycle of container-based virtual machines. It maintains an in-memory dictionary of machine states, coordinates concurrent access using an AsyncLock, and interfaces with the container runtime through ContainerClient. According to the source code in Sources/Services/MachineAPIService/Server/MachinesService.swift, it implements all RPC methods including create, boot, stop, and inspect, while loading persistent state from machine bundles at startup.

How does the Machine API Server handle concurrent requests?

The server handles concurrency through Swift’s actor model and explicit locking. The MachinesService is declared as an actor, providing compile-time data-race protection. Additionally, all mutating operations execute inside lock.withLock { … } blocks to serialize access to the shared machines dictionary. This ensures that concurrent XPC requests from multiple clients cannot corrupt machine state during operations like boot or delete.

What is the difference between MachinesService and MachinesHarness?

MachinesHarness acts as a thin XPC adapter that unmarshals incoming XPCMessage objects and converts them to Swift types before forwarding calls to MachinesService. While MachinesHarness handles transport-specific concerns like JSON encoding and XPC reply formatting in Sources/Services/MachineAPIService/Server/MachinesHarness.swift, MachinesService contains the actual business logic for container lifecycle management. This separation keeps the service layer clean and testable while isolating RPC implementation details.

How are machine states persisted in the apple/container architecture?

Machine states persist through machine bundles stored under <pluginStateRoot>/machines, as implemented in Sources/ContainerPersistence/MachineBundle.swift. Each bundle contains the VM filesystem, configuration snapshots, and log files (stdioLog, bootLog). The MachinesService loads these bundles into memory at startup and writes updates back to disk during lifecycle transitions. Additionally, a state.json file tracks the default machine name, enabling implicit default machine selection in CLI commands.

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 →