Skip to content

Add support for apple-container builder engine - #1935

Open
m21ecom wants to merge 3 commits into
basecamp:mainfrom
m21ecom:feat/add-apple-container-builder-support
Open

Add support for apple-container builder engine#1935
m21ecom wants to merge 3 commits into
basecamp:mainfrom
m21ecom:feat/add-apple-container-builder-support

Conversation

@m21ecom

@m21ecom m21ecom commented Aug 23, 2026

Copy link
Copy Markdown

Adds a builder/engine, defaulting to docker. Setting it to apple-container routes the local build, push and registry commands through Apple's container CLI instead. The deployment servers are untouched and still use Docker.
Two fixes land on the Docker path too: Kamal::Docker.included_files checks status.success? rather than the always-truthy Process::Status it was testing before, and kamal build dev passes push_env the way kamal build push already did.
Verified by hand against Apple's container CLI 1.2.2 on macOS 26. CI covers the Docker paths.

Building with Kamal requires Docker on the developer's machine, which on macOS
means Docker Desktop. Apple's `container` CLI builds and pushes OCI images
natively on Apple silicon, but Kamal hardcodes `docker` for every local build,
push and registry command, so there is no way to use it.

Add `builder/engine`, defaulting to `docker`. Setting it to `apple-container`
routes the commands that run on this machine through `container` instead. The
deployment servers are untouched and still use Docker.

The engine is chosen by picking an object rather than by branching inside the
command classes. Kamal::Commands::Builder#local is the only place the engine
name is read, and it returns Builder::AppleContainer in place of
Builder::Local. Everything downstream is polymorphic on that object: the
builder answers #local_registry with Registry::AppleContainer in place of
Registry, and #install_error with the missing-dependency message for its
engine. Anything that runs on the hosts goes through KAMAL.registry, which
stays Docker-only, so Builder#target, Registry#login and Registry#logout are
unchanged.

- `container build` has no registry exporter, so a push is a build followed by
  one `container image push` per tag.
- `registry/scheme` (auto, http, https) is read only by the apple paths, and
  falls back to http for a localhost registry. `--scheme auto` cannot reach
  one: it attempts TLS, hangs, and fails with "bad protocol version".
- `builder/ssh` is resolved and exported as SSH_AUTH_SOCK, because
  `container build` accepts only the literal `--ssh default` and reads the
  socket from its own environment.
- `kamal build remove` stops the builder rather than deleting it. `container`
  has a single machine-wide builder, so deleting it would discard every
  project's build cache.
- Kamal::Docker.included_files takes the builder and asks it for the check
  image's build and run argv, and checks `status.success?` rather than the
  always-truthy Process::Status it was testing before.
- Unsupported configuration is rejected up front: remote builders, disabled
  local builds, buildpacks, cache exports, provenance and SBOM attestations,
  custom builder drivers, and non-default SSH agents.

`kamal build dev` now passes push_env to the build command, so with a remote
builder it exports BUILDKIT_NO_CLIENT_TOKEN the way `kamal build push` already
did. This affects the Docker path too.

Verified by hand against `container` 1.2.2 on macOS 26: building and pushing
both tags to an authenticated local registry, a multi-arch manifest list from
repeated --platform, and SSH agent forwarding. The test suite was compared
against main by failing-test set rather than by count, and the sets are
identical both on Linux with Ruby 4.0 and on macOS with Docker running.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings August 23, 2026 17:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Apple’s container CLI as an optional local builder engine while retaining Docker on deployment servers.

Changes:

  • Routes local build, push, and registry operations through the configured engine.
  • Adds engine-specific validation, documentation, and tests.
  • Fixes Docker included-file status handling and dev-build environment forwarding.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/fixtures/deploy_with_apple_container.yml Adds an Apple container fixture.
test/docker_test.rb Tests included-file checks and failures.
test/configuration/validation_test.rb Tests engine and scheme validation.
test/configuration/builder_test.rb Tests engine configuration.
test/commands/registry_test.rb Tests Apple registry commands.
test/commands/builder_test.rb Tests Apple builder commands.
test/cli/registry_test.rb Tests mixed local/remote registry behavior.
test/cli/build_test.rb Tests Apple push and dev flows.
lib/kamal/docker.rb Makes included-file checks engine-aware.
lib/kamal/configuration/validator/registry.rb Validates registry schemes.
lib/kamal/configuration/validator/builder.rb Validates engine capabilities.
lib/kamal/configuration/registry.rb Resolves registry schemes.
lib/kamal/configuration/docs/registry.yml Documents registry schemes.
lib/kamal/configuration/docs/builder.yml Documents builder engines.
lib/kamal/configuration/builder.rb Exposes engine configuration.
lib/kamal/commands/registry/apple_container.rb Implements Apple registry commands.
lib/kamal/commands/registry.rb Centralizes the registry container name.
lib/kamal/commands/builder/base.rb Adds shared engine interfaces.
lib/kamal/commands/builder/apple_container.rb Implements the Apple builder.
lib/kamal/commands/builder.rb Selects the configured local engine.
lib/kamal/commands/base.rb Adds Apple CLI command construction.
lib/kamal/commander.rb Exposes engine-specific local registry commands.
lib/kamal/cli/registry.rb Routes local registry operations by engine.
lib/kamal/cli/build.rb Routes local builds and environments by engine.
lib/kamal/cli/base.rb Generalizes dependency checking.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +61 to +67
def push_env
if (socket = ssh_socket)
{ "SSH_AUTH_SOCK" => socket }
else
{}
end
end

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in f33e335.

The suggested patch raises NameError though — Ruby parses socket in the body as a
method call, since the assignment is lexically later on the line. Assigning first works:

socket = ssh_socket

{ "CONTAINER_DEFAULT_PLATFORM" => "" }.tap do |env|
  env["SSH_AUTH_SOCK"] = socket if socket
end

Comment thread lib/kamal/configuration/registry.rb Outdated
Comment on lines +22 to +23
# `auto` is the container CLI's own default, and it cannot reach a local
# registry: it attempts TLS and fails. Treat it as "let Kamal decide".

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reproducible on container 1.2.2 / macOS 26.6.2. Against a plain-HTTP registry:3 on 127.0.0.1:5099:

container image push localhost:5099/probe:1               -> Error: -9836: bad protocol version
container image push 127.0.0.1:5099/probe:1               -> Error: -9836: bad protocol version
container image push --scheme http localhost:5099/probe:1 -> 100% (4 blobs, 1,8 MB) [0s]

Only the explicit-HTTP push landed. Keeping the behaviour; the comment now names the error, in a1e5c30.

m21ecom and others added 2 commits August 24, 2026 10:04
The push commands carry no --platform: they push whatever tags the build
produced, so a multi-arch build sends a manifest list. `container` fills a
missing --platform from CONTAINER_DEFAULT_PLATFORM, so a developer with that
variable exported would push one architecture under a tag Kamal built for two,
and the servers on the other architecture would fail to pull it.

Export the variable empty for the build-and-push command, which 1.2.2 reads as
unset. The build itself is unaffected either way, because the --platform flags
it passes take precedence over the variable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Naming the failure makes it clear why Kamal picks http for a localhost registry
rather than leaving the scheme to `container`: 1.2.2 attempts TLS for localhost
and 127.0.0.1 alike, and fails with "bad protocol version".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants