# How the Apple Container Build System Uses BuildKit: A Complete Technical Guide

> Discover how Apple's container build system leverages BuildKit. Our technical guide explains its use of BuildKit via gRPC API and a dedicated container.

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

---

**The Apple container toolchain implements its image-building workflow on top of BuildKit by managing a dedicated BuildKit container, connecting via vsock on port 8088, and driving builds through the BuildKit gRPC API.**

The `apple/container` repository provides a container toolchain that delegates all image building to BuildKit, a high-performance OCI build engine. This article explains how the build system uses BuildKit by examining the Swift source code that manages the builder lifecycle, establishes vsock connections, and translates CLI flags into BuildKit gRPC calls.

## The Three-Phase BuildKit Integration

The integration between the container CLI and BuildKit consists of three tightly coupled phases: container management, socket connection, and build orchestration.

### Phase 1: Launching the BuildKit Container

The `container builder start` command (or the automatic fallback inside `container build`) ensures a BuildKit daemon is running inside a dedicated container named **`buildkit`**. According to the source code in [[`Sources/ContainerCommands/Builder/BuilderStart.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Builder/BuilderStart.swift)](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Builder/BuilderStart.swift), the implementation follows this sequence:

1. **Parse resource flags** (lines 40‑50): CPU, memory, DNS, and other configuration options are extracted from CLI arguments.
2. **Check existing container** (lines 69‑86): The system checks if a container named `buildkit` already exists via `client.get(id: "buildkit")`. If it is running but the image, resources, or environment variables have changed, the container is stopped and removed to ensure configuration consistency.
3. **Pull and configure** (lines 101‑165): The configured build image (`containerSystemConfig.build.image`) is fetched via `ClientImage.fetch`, unpacked, and configured with a `ContainerConfiguration` that specifies the **container-builder-shim** executable as the entry point. The configuration mounts a temporary `/run` tmpfs and a virtiofs export directory, while injecting BuildKit-specific environment variables (`BUILDKIT_COLORS`, `NO_COLOR`).
4. **Start the daemon** (lines 291‑340): The container is created via `client.create`, and the private helper `startBuildKit` boots the BuildKit daemon inside the container. The shim handles bootstrap and forwards stdin/stdout over the vsock channel.

### Phase 2: Connecting to the BuildKit Daemon

Once the container is running, the `container build` command must establish communication with the BuildKit daemon. In [[`Sources/ContainerCommands/BuildCommand.swift`](https://github.com/apple/container/blob/main/Sources/ContainerCommands/BuildCommand.swift)](https://github.com/apple/container/blob/main/Sources/ContainerCommands/BuildCommand.swift), the connection logic implements automatic recovery:

- The command creates a `ProgressBar` and displays "Dialing builder" (line 65).
- It launches a **task group** that repeatedly attempts to dial the BuildKit container on the vsock port (`client.dial(id: "buildkit", port: vsockPort)`), defaulting to port **8088**.
- On the first failure, the group triggers `BuilderStart.start(...)` to launch the builder container (lines 91‑99), then sleeps 5 seconds before retrying (lines 100‑102).
- Once the dial succeeds, a `Builder` instance wrapping the vsock file handle is returned (lines 84‑85).

This design ensures that `container build` is resilient: it automatically starts the builder if it is not already running.

### Phase 3: Driving the Build via BuildKit gRPC

With a live `Builder` client, the build command orchestrates the actual image construction:

- **Input preparation** (lines 24‑52): The Dockerfile (or stdin) and optional `.dockerignore` file are read and validated.
- **Configuration packing** (lines 54‑74): A `Builder.BuildConfig` struct is constructed, mirroring all CLI flags including build args, secrets, platforms, caches, tags, and output exporters.
- **gRPC execution**: The `builder.build(config)` method, defined in the **ContainerBuild** module at [[`Sources/ContainerBuild/Builder.swift`](https://github.com/apple/container/blob/main/Sources/ContainerBuild/Builder.swift)](https://github.com/apple/container/blob/main/Sources/ContainerBuild/Builder.swift), serializes the config into BuildKit gRPC messages and streams the build context over the vsock channel.
- **Result handling** (lines 94‑140): After the build completes, the CLI unpacks the exported image(s) using `ClientImage.load` and applies the appropriate tags.

## Automatic Builder Lifecycle Management

The build system handles configuration drift automatically. If the builder container is already running, `container build` skips the start step and connects directly. However, the `BuilderStart` logic in [[`BuilderStart.swift`](https://github.com/apple/container/blob/main/BuilderStart.swift)](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Builder/BuilderStart.swift) guarantees that any configuration changes—different image, altered CPU/memory limits, or modified BuildKit environment variables—force a fresh restart, keeping the builder state consistent with user-specified options.

For manual lifecycle control, the repository provides [[`BuilderStop.swift`](https://github.com/apple/container/blob/main/BuilderStop.swift)](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Builder/BuilderStop.swift) and [[`BuilderDelete.swift`](https://github.com/apple/container/blob/main/BuilderDelete.swift)](https://github.com/apple/container/blob/main/Sources/ContainerCommands/Builder/BuilderDelete.swift) to stop or remove the builder container.

## Practical Code Examples

### Manually Start a Builder Container

Allocate specific resources to the BuildKit daemon:

```bash

# Allocate 4 CPUs and 4 GiB of memory

container builder start --cpus 4 --memory 4G

```

Behind the scenes, `BuilderStart.start` pulls the image defined in `containerSystemConfig.build.image`, creates the container with the requested resources, and executes `/usr/local/bin/container-builder-shim` inside it.

### Build an Image with Automatic Builder Launch

Build and tag an image with custom resources:

```bash

# Build with custom Dockerfile and 2 CPUs for the builder

container build -f Dockerfile.custom -t myapp:latest --cpus 2 .

```

The execution flow is:
1. `BuildCommand.run` attempts to dial the builder vsock.
2. On failure, it calls `BuilderStart.start(cp...)` (lines 91‑99).
3. After the builder is up, the Dockerfile and context are streamed to BuildKit.
4. The resulting OCI image is stored locally and tagged as `myapp:latest`.

### Check Builder Status

Inspect the current state of the BuildKit container:

```bash
container builder status

```

This reads the container state of the `buildkit` container and reports `running`, `stopped`, or other states (implemented in [`BuilderStatus.swift`](https://github.com/apple/container/blob/main/BuilderStatus.swift)).

### Force-Delete a Running Builder

Remove the builder container to force a clean slate:

```bash
container builder delete --force

```

The `BuilderDelete` command removes the builder container regardless of its state, ensuring that the next `container build` invocation creates a fresh instance with updated configuration.

## Summary

- The Apple container build system delegates all image building to BuildKit through a dedicated container named `buildkit`.
- **BuilderStart.swift** manages the container lifecycle, handling resource allocation, image pulling, and shim execution via the `startBuildKit` helper (lines 307‑340).
- **BuildCommand.swift** establishes vsock connections on port 8088, with automatic retry logic that launches the builder if it is not running (lines 91‑102).
- The **ContainerBuild** module translates `Builder.BuildConfig` into BuildKit gRPC calls, streaming the build context and returning OCI images via `ClientImage.load`.
- Configuration drift is automatically detected, forcing container recreation when resources or environment variables change.

## Frequently Asked Questions

### What port does the build system use to connect to BuildKit?

The build system connects to the BuildKit daemon via vsock on **port 8088** by default. The `BuildCommand` implementation in [`BuildCommand.swift`](https://github.com/apple/container/blob/main/BuildCommand.swift) uses `client.dial(id: "buildkit", port: vsockPort)` to establish this connection, with automatic retry logic that waits 5 seconds between attempts if the daemon is not yet ready.

### How does the build system handle configuration changes to the builder?

If the existing `buildkit` container is running but its configuration—such as CPU limits, memory allocation, container image, or BuildKit environment variables like `BUILDKIT_COLORS`—has changed, the system automatically stops and removes the old container. This logic, implemented in [`BuilderStart.swift`](https://github.com/apple/container/blob/main/BuilderStart.swift) (lines 69‑86), ensures that the builder state always matches the user's current specifications.

### Can I run the BuildKit builder manually without building an image?

Yes. You can explicitly start the builder using `container builder start`, optionally specifying resource limits like `--cpus` and `--memory`. This command pulls the configured build image, creates the container with the `container-builder-shim` entry point, and boots the BuildKit daemon. You can then check its status with `container builder status` or remove it with `container builder delete`.

### What protocol does the client use to communicate with BuildKit?

The client communicates with BuildKit using the **BuildKit gRPC protocol**. The `Builder` class defined in [`Sources/ContainerBuild/Builder.swift`](https://github.com/apple/container/blob/main/Sources/ContainerBuild/Builder.swift) wraps the vsock file handle and serializes the `Builder.BuildConfig` into gRPC messages. This includes all build parameters such as Dockerfile content, build arguments, secrets, target platforms, and cache configurations, which are streamed to the daemon for execution.