# How the Container CLI Communicates with the API Server: gRPC over Unix Domain Sockets

> Discover how the container CLI communicates with the API server using gRPC over Unix domain sockets. Learn about protobuf marshaling and HTTP/2 communication without custom logic.

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

---

**The container CLI communicates with the API server via gRPC over a Unix domain socket, using the `ContainerAPIClient` library to marshal protobuf requests through HTTP/2 without requiring custom HTTP logic.**

The `apple/container` repository implements a container runtime architecture where the command-line interface (CLI) and the API server (`container-apiserver`) run as independent Swift executables on the same host. This design relies on a type-safe Remote Procedure Call (RPC) mechanism that keeps the CLI lightweight while delegating container operations to the server process.

## The Communication Architecture

The CLI and API server communicate through a **local Unix domain socket** rather than TCP ports or REST endpoints. This architecture provides several technical advantages:

- **Transport Layer**: HTTP/2 gRPC via Unix domain socket (`/var/run/container.sock` by default)
- **Client Library**: `ContainerAPIClient` containing auto-generated gRPC stubs
- **Serialization**: Protocol Buffers (protobuf) for message exchange
- **Network Stack**: SwiftNIO via GRPCSwift (grpc-swift-2) for asynchronous I/O

When you execute a command like `container start`, the CLI creates a client from the generated stubs and sends RPC calls over the socket. The server receives these protobuf messages, executes the corresponding container operation, and returns serialized responses.

## Client-Side Implementation

### Entry Point and Command Routing

The CLI entry point resides in [`Sources/CLI/ContainerCLI.swift`](https://github.com/apple/container/blob/main/Sources/CLI/ContainerCLI.swift), where the `@main` struct creates an `Application` using ArgumentParser. This application registers all sub-commands in [`Sources/ContainerCommands/Application.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Application.swift) (generated by ArgumentParser), mapping each CLI verb to its corresponding implementation.

For example, when you run `container start`, the command resolves to [`ContainerStart.swift`](https://github.com/apple/container/blob/main/ContainerStart.swift) in `Sources/ContainerCommands/Container/`.

### Establishing the gRPC Connection

Each sub-command instantiates a client using the `ContainerAPIClient` library. In [`Sources/ContainerCommands/Container/ContainerStart.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Container/ContainerStart.swift), the implementation follows this pattern:

```swift
import ContainerAPIClient

let client = try ContainerAPIClient.makeClient(socketPath: "/var/run/container.sock")
let request = ContainersService_StartRequest(containerID: id)
let response = try await client.containers.start(request)

```

The `makeClient` method internally constructs a `GRPCChannel` defined in [`Sources/ContainerAPIClient/GRPCChannel.swift`](https://github.com/apple/container/blob/main/Sources/ContainerAPIClient/GRPCChannel.swift):

```swift
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let channel = try GRPCChannelPool.withUnixDomainSocket(path: socketPath, group: group)

```

This channel uses **SwiftNIO** (`NIOCore`) to manage the Unix socket connection, while **GRPCSwift** handles the HTTP/2 framing and gRPC protocol specifics.

### Generated Service Stubs

The actual RPC methods are defined in [`Sources/Services/ContainerAPIService/Client/ContainersService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/ContainersService.swift), which contains the auto-generated `ContainersServiceClient` class. This stub is generated from the protobuf definitions (e.g., `containers.proto`) and provides type-safe methods like `start()`, `list()`, and `run()` that the CLI calls directly.

## Server-Side Implementation

### Binding to the Unix Socket

The API server initialization occurs in `Sources/APIServer/APIServer+Start.swift`. The `APIServer.main()` method constructs a `GRPCServer` and binds it to the Unix socket path:

```swift
// In Sources/APIServer/APIServer+Start.swift
let server = GRPCServer(group: group, serviceProviders: [ContainersServiceProvider()])
try server.bind(unixDomainSocketPath: "/var/run/container.sock")

```

### Handling Incoming Requests

The server implementation in [`Sources/Services/ContainerAPIService/Server/ContainersService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Server/ContainersService.swift) registers the same protobuf service interface. When the CLI sends an RPC, the server receives the deserialized protobuf message, executes the container operation using the runtime engine, and serializes the response back to the client over the same HTTP/2 connection.

## Auxiliary HTTP Communication

While container operations use gRPC, file transfers such as image pulls and kernel downloads use a separate HTTP client. The `ContainerAPIClient.FileDownloader` class (defined in [`Sources/ContainerAPIClient/FileDownloader.swift`](https://github.com/apple/container/blob/main/Sources/ContainerAPIClient/FileDownloader.swift)) utilizes **AsyncHTTPClient** to perform standard HTTP GET/PUT operations. These calls are independent of the gRPC channel and do not travel through the Unix socket to the API server.

## Configuration and Socket Path

By default, the CLI and server communicate through `/var/run/container.sock`. You can specify an alternative path using the `--socket` flag:

```bash

# Start the server on a custom socket

container-apiserver --socket /tmp/custom.sock &

# CLI automatically uses the same socket path

container --socket /tmp/custom.sock list

```

The entire exchange remains within the host kernel's networking stack, avoiding TCP overhead and providing lower latency than network-based communication.

## Summary

- The container CLI uses the **`ContainerAPIClient`** library to communicate with the API server via **gRPC over HTTP/2**.
- Transport occurs over a **Unix domain socket** (`/var/run/container.sock` by default) using SwiftNIO and GRPCSwift.
- Service definitions are generated from **Protocol Buffer** files, creating type-safe client stubs in `Sources/Services/ContainerAPIService/Client/`.
- The server binds to the same socket in `Sources/APIServer/APIServer+Start.swift` and handles requests in `Sources/Services/ContainerAPIService/Server/`.
- File download operations (images, kernels) use **AsyncHTTPClient** separately from the gRPC channel.

## Frequently Asked Questions

### Does the container CLI use REST or HTTP directly?

No. According to the `apple/container` source code, the CLI does not use REST or raw HTTP for container operations. Instead, it uses **gRPC** (HTTP/2) via the `ContainerAPIClient` library, with messages serialized using Protocol Buffers. The only HTTP usage occurs in `FileDownloader` for downloading image layers and kernel tarballs.

### What socket path does the container CLI use by default?

The default Unix domain socket path is **`/var/run/container.sock`**. This path is used unless overridden by the `--socket` command-line flag when starting `container-apiserver` or running CLI commands. The socket path must match between the server and client for communication to succeed.

### How are the RPC methods defined between the CLI and server?

RPC methods are defined in **protobuf** files (e.g., `containers.proto`) and compiled into Swift using the gRPC Swift plugin. The generated client stubs reside in [`Sources/Services/ContainerAPIService/Client/ContainersService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Client/ContainersService.swift), while server implementations are in [`Sources/Services/ContainerAPIService/Server/ContainersService.swift`](https://github.com/apple/container/blob/main/Sources/Services/ContainerAPIService/Server/ContainersService.swift). Both sides share the same protobuf message definitions ensuring type safety.

### Why does the Apple Container project use gRPC instead of REST?

The project uses gRPC to provide **strongly-typed, contract-based APIs** with automatic code generation for both client and server. This eliminates manual HTTP endpoint management and JSON parsing, reducing bugs and maintenance overhead. The HTTP/2 transport also supports efficient multiplexing over the single Unix domain socket connection.