How the BuildKit Builder Operates in apple/container and Configures Build Resources

The BuildKit builder in apple/container is an isolated OCI container managed by the container CLI, which loads default resources from ContainerSystemConfig, overrides them via CLI flags parsed in Parser.swift, and orchestrates the builder lifecycle through BuilderStart.swift by fetching images, configuring resources, and booting the BuildKit daemon.

The apple/container project provides a dedicated BuildKit builder that isolates image builds within a specialized container environment. When you execute container builder commands, the Swift implementation in BuilderStart.swift coordinates resource allocation, container lifecycle management, and daemon initialization. Understanding how the BuildKit builder operates and configures build resources requires examining the command implementation alongside the resource parsing logic in Parser.swift.

Loading Global System Configuration

In Sources/ContainerCommands/Builder/BuilderStart.swift, the BuilderStart.start method initiates the builder lifecycle by reading the global system configuration. The code calls Application.loadContainerSystemConfig() to obtain the default BuildKit container image, default CPU count, and default memory size for the builder instance.

This configuration serves as the fallback for all resource limits when explicit values are not provided via command-line flags. The default values are accessed around lines 58-62 in BuilderStart.swift, establishing the baseline hardware constraints for the BuildKit environment.

Parsing Build Resources from CLI Flags

Resource allocation is determined by the Parser.resources function located in Sources/Services/ContainerAPIService/Client/Parser.swift. This helper processes the user-supplied --cpus and --memory flags, merging explicit values with the defaults loaded from ContainerSystemConfig.

The implementation handles the following logic:

  • If a flag is omitted, the system default (containerSystemConfig.build.cpus or containerSystemConfig.build.memory) is applied
  • The function constructs a ContainerConfiguration.Resources struct where cpus is an Int and memoryInBytes is calculated from the MiB value provided
  • Parsing occurs at lines 5-23 in Parser.swift

Managing the Builder Container Lifecycle

The builder container is identified by the hardcoded name "buildkit". The BuilderStart.start method queries the container state using client.get(id: "buildkit") and implements intelligent reuse logic defined around lines 49-84:

  • Running with mismatched configuration: If the container is active but differs in image, CPU, memory, environment, or DNS settings, the code stops and deletes the existing container before creating a fresh one
  • Stopped with matching configuration: If the container exists in a stopped state and matches the desired configuration, the system simply restarts it via startBuildKit
  • Non-existent: A new container is created from scratch

This ensures that resource changes are immediately applied without manual cleanup of stale containers.

Fetching and Configuring the BuildKit Image

When creating a new builder instance, the system performs a multi-stage initialization sequence (lines 94-119 in BuilderStart.swift):

  1. Fetch: The BuildKit image is retrieved from the registry using ClientImage.fetch with the platform arm64/linux
  2. Unpack: The image is prepared via image.getCreateSnapshot before container creation
  3. Progress: Real-time updates are emitted through ProgressUpdateHandler to display "Fetching BuildKit image" and "Unpacking BuildKit image" status messages

Following image preparation, a ContainerConfiguration is assembled (lines 124-148) with the following specifications:

  • Resources: The Resources struct from the parsing stage (config.resources = resources)
  • Mounts: Tmpfs mount at /run and the virtiofs export directory
  • Labels: Identification markers plugin:builder and role:builder
  • Capabilities: Granted ALL capabilities with optional Rosetta support
  • Networking: Attachment to the default network with CLI-supplied DNS settings

Launching the BuildKit Daemon

The startBuildKit helper function (defined at the end of BuilderStart.swift, lines 306-340) completes the initialization by:

  1. Booting the builder shim process inside the container using client.bootstrap
  2. Starting the actual BuildKit daemon process
  3. Implementing error handling that tears down the container via client.stop and client.delete if startup fails

This ensures the BuildKit daemon runs with the precise resource constraints and configuration assembled in previous steps.

CLI Commands for Builder Management

The command-line interface documented in docs/command-reference.md (lines 702-754) exposes three primary builder sub-commands:

  • container builder start: Accepts -c/--cpus and -m/--memory flags to override defaults, pulls the image, creates the container, and initializes BuildKit
  • container builder status: Displays the current builder state and configuration in a human-readable table format
  • container builder stop and container builder delete: Manage container lifecycle, with delete supporting --force to remove running instances

# Start the builder with 4 CPUs and 8 GiB of RAM

container builder start -c 4 -m 8G

# Show builder status (human-readable table)

container builder status

# Stop the builder (keeps the container image for later reuse)

container builder stop

# Force-delete the builder, discarding the container

container builder delete --force

For programmatic control from Swift tools:

import ContainerCommands

try await BuilderStart.start(
    cpus: 2,                     // optional – overrides config default
    memory: "4G",                // optional – overrides config default
    log: Logger(label: "example"),
    dnsNameservers: ["1.1.1.1"],
    dnsDomain: nil,
    dnsSearchDomains: [],
    dnsOptions: [],
    progressUpdate: { _ in },
    containerSystemConfig: try await Application.loadContainerSystemConfig()
)

Summary

  • The BuildKit builder operates as a standard OCI container named "buildkit" managed by the container client
  • Default resources are defined in ContainerSystemConfig and loaded via Application.loadContainerSystemConfig() in BuilderStart.swift
  • CLI flags --cpus and --memory are parsed by Parser.resources to create a ContainerConfiguration.Resources struct that overrides defaults
  • The lifecycle manager intelligently reuses stopped containers with matching configurations or recreates them when resource limits change
  • Image fetching uses ClientImage.fetch with arm64/linux platform targeting, followed by unpacking via getCreateSnapshot
  • Container configuration includes resource limits, mounts, labels, capabilities, and DNS settings assembled in BuilderStart.swift
  • The daemon is launched via startBuildKit using client.bootstrap, with automatic teardown on failure

Frequently Asked Questions

How do I specify custom CPU and memory limits for the BuildKit builder?

Pass the -c (or --cpus) and -m (or --memory) flags to the container builder start command. For example, container builder start -c 4 -m 8G allocates 4 CPU cores and 8 GiB of RAM. These values override the defaults stored in ContainerSystemConfig.build.cpus and ContainerSystemConfig.build.memory.

What happens if I change the builder configuration while it's running?

If the existing "buildkit" container is running but its configuration differs from the requested resources (CPU, memory, image, environment, or DNS), the BuilderStart.start method automatically stops and deletes the existing container before creating a new one with the updated specifications. If the container is stopped but matches the desired configuration, it is simply restarted.

Where are the default builder resource limits defined?

Default limits are stored in the global ContainerSystemConfig structure, accessed via Application.loadContainerSystemConfig() at the beginning of the BuilderStart.start function. The configuration specifies default values for build.cpus and build.memory that apply when CLI flags are omitted.

How does the container tool determine which BuildKit image to use?

The tool uses the default BuildKit image specified in ContainerSystemConfig, fetched during the builder creation phase. The implementation in BuilderStart.swift calls ClientImage.fetch with the platform set to arm64/linux to pull the appropriate image from the registry before unpacking it with image.getCreateSnapshot.

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 →