Files
bot-bottle/docs/prds/prd-new-macos-container-backend.md
T

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-container and BOT_BOTTLE_BACKEND=macos-container are accepted by the existing backend selector.
  • Backend availability is true only on macOS hosts with container on PATH.
  • The backend has tested wrappers for Apple Container image build, image inspection, container exec, container cp, 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"
  • container is discoverable on PATH

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

  1. Register macos-container, add availability/preflight, bottle handle, utility wrappers, cleanup, active enumeration, unit tests, and this PRD.
  2. 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.
  3. Implement launch once the enforcement shape is proven. Reuse the existing sidecar bundle image and daemon subset env contract where possible.
  4. Add real-runtime integration tests guarded by container presence and macOS version.
  5. Consider moving smolmachines sidecar/image-building work to VM-contained or Apple Container-backed execution only after the macos-container launch 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 MacosContainerBottle command 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 RUN steps inherit a working resolver.

References

  • Issue #220 review comment: smolvm --port/-p can 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.