7.6 KiB
PRD prd-new: macOS Container backend
- Status: Draft
- Author: Codex
- Created: 2026-06-10
- Issue: #220
Summary
Add an experimental macos-container backend that integrates Apple's
container CLI as a host runtime on macOS. The first shipped slice
registers the backend and implements reusable host primitives
(build, exec, cp, image inspection, cleanup, active
enumeration). Follow-up slices make launch runnable with the proven
two-network sidecar topology and add real-runtime coverage, without
weakening bot-bottle's sidecar egress model.
Problem
bot-bottle currently has two local execution paths:
docker, which runs the whole bottle topology through Docker Compose.smolmachines, which runs the agent in smolvm but still depends on Docker for the sidecar bundle and image-building pipeline.
Issue #220 explored removing Docker as a host dependency. A follow-up
review comment verified that smolvm can publish guest ports back to
host loopback and that another smolvm guest can reach that service
through the existing per-bottle loopback alias plus --allow-cidr
path. That keeps the VM-contained sidecar direction viable and rejects
the host-process sidecar fallback.
Apple's container CLI is another macOS-native way to run OCI images
as lightweight Linux VMs. Its current command surface includes
Docker-like build, run, exec, cp, port publishing, image
inspection, and user-defined networks. That makes it a plausible local
backend, but it does not remove the need to preserve bot-bottle's
sidecar enforcement property: the agent must not have a direct egress
path around the egress sidecar.
Goals / Success Criteria
--backend=macos-containerandBOT_BOTTLE_BACKEND=macos-containerare accepted by the existing backend selector.- Backend availability is true only on macOS hosts with
containeronPATH. - The backend has tested wrappers for Apple Container image build,
image inspection, container
exec, containercp, cleanup, and active-agent enumeration. - Full launch uses a host-only internal network for the agent and a separate NAT egress network for the sidecar bundle.
- The agent container does not attach to the egress network. It reaches allowed outbound hosts through HTTP(S)_PROXY pointing at the sidecar's internal-network IP.
bottle.git/ git-gate bottles fail loudly on this backend until a safe Apple Container key-delivery path exists.- Real-runtime integration coverage is present and guarded by macOS and Apple Container availability.
Non-goals
- Do not remove or deprecate the Docker backend.
- Do not change the default backend from
smolmachines. - Do not run sidecar daemons as host processes.
- Do not launch a degraded backend where the agent can bypass the egress sidecar through direct network access.
- Do not require Docker Desktop as part of the macOS Container backend.
Design
Backend name
The selectable backend name is macos-container. The Python package
uses bot_bottle.backend.macos_container because module names cannot
contain hyphens.
Availability and preflight
MacosContainerBottleBackend.is_available() returns true only when:
platform.system() == "Darwin"containeris discoverable onPATH
prepare() calls require_container(), which produces a concrete
install pointer and rejects non-macOS hosts.
Implemented primitives
The backend owns an Apple Container wrapper module instead of reusing Docker wrappers. The wrapper maps bot-bottle's backend needs to Apple's CLI:
| bot-bottle need | Apple Container command |
|---|---|
| Build provider image | container build -t <ref> [-f Dockerfile] <context> |
| Run agent commands | container exec [--interactive --tty] <id> ... |
| Copy files into guest | container cp <host> <id>:<path> |
| Inspect image identity | container image inspect <ref> |
| Cleanup stale containers | container delete --force <id> |
| Cleanup stale networks | container network delete <name> |
| Active enumeration | container list --quiet |
The bottle handle mirrors DockerBottle: it builds a host argv for
foreground agent execution, pipes shell snippets through stdin for
Bottle.exec, and exposes cp_in for provisioning.
Launch topology
launch() uses Apple Container's two-network topology:
- create a host-only internal network for the bottle;
- create a normal NAT egress network for the sidecar bundle;
- start the sidecar bundle attached to the egress network first and the internal network second;
- discover the sidecar's internal-network IPv4 address from
container inspect; - start the agent attached only to the internal network, with HTTP_PROXY / HTTPS_PROXY / lowercase proxy vars pointing at the sidecar IP and egress port.
This keeps the agent off the outbound network while preserving the proxy-env contract that existing agent tooling already honors. The integration smoke also removes the proxy env in-guest and confirms direct egress fails.
Deferred git-gate support
Apple Container currently rejects single-file bind mounts, and
container cp into a stopped container is not available. Starting the
container earlier would allow container cp into a running container,
but it would also mean delivering SSH private key material into a live
sidecar before the git-gate daemon is ready to own it. Mounting broad
host SSH directories is not acceptable.
For this PRD, bottle.git / git-gate support is explicitly deferred on
the macos-container backend. Bottles with git-gate upstreams fail
loudly and should use docker or smolmachines until a narrower key
delivery design lands.
Implementation chunks
- Register
macos-container, add availability/preflight, bottle handle, utility wrappers, cleanup, active enumeration, unit tests, and this PRD. - Spike Apple Container networking against real macOS 26 hosts:
repeated
--network, internal network egress behavior, published loopback reachability from another container, DNS behavior, and labels/JSON output stability. - Implement launch once the enforcement shape is proven. Reuse the existing sidecar bundle image and daemon subset env contract where possible.
- Add real-runtime integration tests guarded by
containerpresence and macOS version. - Consider moving smolmachines sidecar/image-building work to
VM-contained or Apple Container-backed execution only after the
macos-containerlaunch path is trustworthy.
Testing Strategy
- Unit tests cover backend registration through
known_backend_names. - Unit tests cover availability/preflight behavior without requiring macOS.
- Unit tests cover
MacosContainerBottlecommand construction and stdin-based shell execution. - Unit tests cover cleanup and active enumeration parsing.
- Unit tests cover launch argv/env construction, sidecar mount staging, sidecar IP parsing, and git-gate rejection.
- Integration tests run on macOS hosts with Apple Container installed
and verify that egress cannot bypass the sidecar. They also preflight
Apple Container BuildKit DNS because image builds must resolve
package mirrors before a launch smoke can be meaningful. The backend
starts/restarts the Apple Container builder with the configured DNS
server before image builds so BuildKit
RUNsteps inherit a working resolver.
References
- Issue #220 review comment:
smolvm
--port/-pcan expose a guest service to host loopback, and another smolvm guest can reach it through the existing per-bottle loopback alias path. - Apple Container command reference:
container run,build,exec, port publishing, and network commands.