Files
bot-bottle/docs/prds/prd-new-consolidate-docker-infra-backend.md
T
didericis-claude 52c0df7741
test / stage-firecracker-inputs (pull_request) Successful in 4s
test / integration-docker (pull_request) Successful in 13s
tracker-policy-pr / check-pr (pull_request) Successful in 14s
test / unit (pull_request) Successful in 37s
lint / lint (push) Failing after 2m41s
test / build-infra (pull_request) Successful in 3m59s
test / integration-firecracker (pull_request) Successful in 1m35s
test / coverage (pull_request) Successful in 2m7s
test / publish-infra (pull_request) Has been skipped
feat(docker): consolidate to single infra container under gateway_init supervise tree
Collapses the two-container Docker model (gateway + orchestrator) into one
bot-bottle-infra container, matching the macOS and Firecracker backends.

- Dockerfile.infra: now a shared gateway+orchestrator base (COPY bot_bottle
  from orchestrator build, no CMD override)
- Dockerfile.infra.fc: new Firecracker-specific layer (buildah/crun/netavark)
- gateway_init: adds orchestrator daemon with _OPT_IN_DAEMONS gating so it
  only starts when BOT_BOTTLE_GATEWAY_DAEMONS explicitly includes it
- orchestrator/lifecycle: OrchestratorService manages one infra container;
  builds orchestrator (intermediate) then infra; live source bind-mounted at
  /bot-bottle-src with PYTHONPATH so the subprocess uses the checkout
- backend/consolidated_util: extracts provision_bottle + teardown_consolidated
  shared across all three backends; removes duplication in docker/fc/macos
  consolidated_launch modules
- firecracker/infra_vm: builds four images (orchestrator→gateway→infra→infra.fc)
- All unit tests updated and passing (1878 tests)
- PRD status: Draft → Active
2026-07-20 15:36:37 -04:00

6.7 KiB

PRD prd-new: Consolidate infra backend for Docker

  • Status: Active
  • Author: Claude
  • Created: 2026-07-20
  • Issue: #431

Summary

The Docker backend runs two containers — bot-bottle-orch-gateway (gateway data plane) and bot-bottle-orchestrator (control plane) — where the macOS and Firecracker backends already run a single combined infra unit. This PRD collapses Docker to the same model: one bot-bottle-infra container running both processes under the gateway_init supervise tree, a restructured Dockerfile.infra as the shared gateway+orchestrator base, and a handful of extracted shared utilities (CA cert polling, teardown sequence, launch skeleton) that are currently duplicated across all three consolidated_launch.py files.

Goals / success criteria

  • Docker backend starts exactly one infra container instead of two.
  • Dockerfile.infra is the shared base image (gateway + orchestrator, no buildah); the Firecracker image layers buildah on top of it.
  • The orchestrator process runs under the gateway_init supervise tree inside the combined container (one PID-1, one restart/health surface).
  • CA cert polling, the teardown sequence, and the shared launch skeleton (ensure-infra → register → provision → return context) live in a single shared module; all three backends import from it.
  • No functional change to macOS or Firecracker launch paths.

Non-goals

  • Changing per-bottle isolation — agents stay one-VM/container-each.
  • Consolidating transport implementations (DockerGatewayTransport, AppleGatewayTransport, SshGatewayTransport) — these are already the right abstraction boundary.
  • macOS DHCP-inversion of registration order — irreducible backend difference, stays as-is.
  • Any changes to the orchestrator RPC protocol or the attribution model.

Design

Dockerfile restructuring

Current shape:

  • Dockerfile.gateway — data plane (mitmproxy, gitleaks, git, openssh, supervise daemons)
  • Dockerfile.orchestrator — control plane (python:3.12-slim + bot_bottle package; stdlib-only, no third-party deps)
  • Dockerfile.infra — Firecracker only: FROM bot-bottle-gateway + buildah + COPY --from bot-bottle-orchestrator

New shape:

  • Dockerfile.gateway — unchanged
  • Dockerfile.orchestrator — unchanged (single definition of orchestrator content; both Docker infra and Firecracker infra COPY --from it)
  • Dockerfile.infrashared base: FROM bot-bottle-gateway + COPY --from bot-bottle-orchestrator (no buildah — Docker infra image)
  • Dockerfile.infra.fc — Firecracker only: FROM bot-bottle-infra + buildah/crun/netavark/aardvark-dns (layered on the shared base, same net result as today)

The comment in Dockerfile.infra that says "the docker backend keeps orchestrator + gateway as separate images; this combined image exists only for the Firecracker single-VM cut" is removed.

Orchestrator in the supervise tree

gateway_init already supervises the data-plane daemons (egress, git-http, supervise-MCP). The orchestrator control plane is added as another supervised process: python3 -m bot_bottle.orchestrator --host 0.0.0.0 --port <port> --broker stub.

The orchestrator source is bind-mounted (/app → repo root, as today) so dev live-reload still works. source_hash-based container recreation in OrchestratorService.ensure_running continues to apply — a code change recreates the combined infra container, which bounces both gateway and orchestrator. This is acceptable: the docker backend is a dev/legacy target where in-flight egress connections across a code deploy are not a hard requirement.

OrchestratorService changes

OrchestratorService currently starts two containers in sequence: gateway first (DockerGateway.ensure_running), then orchestrator. After this PRD:

  • Single docker run of bot-bottle-infra:latest
  • Container name: bot-bottle-infra (replaces bot-bottle-orch-gateway + bot-bottle-orchestrator)
  • Published ports: 127.0.0.1:{port}:{port} for the control plane (same as today)
  • Bind mounts: repo root + host root (same as today)
  • DockerGateway becomes an implementation detail of OrchestratorService rather than a separately started container; the gateway image name (GATEWAY_IMAGE) is no longer referenced at runtime, only at build time for the Dockerfile.infra base

The _gateway() / ensure_running two-step in OrchestratorService is replaced by a single _run_infra_container().

Shared backend utilities

Three items are duplicated across backend/docker/consolidated_launch.py, backend/macos_container/consolidated_launch.py, and backend/firecracker/consolidated_launch.py:

  1. CA cert polling loopdeadline = time.monotonic() + timeout; while ...: try fetch CA; sleep — extracted to backend/consolidated_util.py:poll_ca_cert(transport, *, timeout).

  2. Teardown sequenceOrchestratorClient(url).teardown_bottle(id) + deprovision_git_gate(transport, id) — extracted to backend/consolidated_util.py:teardown_consolidated(url, transport, bottle_id).

  3. Launch skeleton — all three follow: ensure-infra → allocate/register → provision git-gate → fetch CA cert → return launch context. The macOS inversion (agent starts before registration, source IP from DHCP) is the only deviation. Extract a shared _provision_bottle(transport, bottle_id, plan, orchestrator_url) helper covering the register → provision → return-token steps; the backends keep their own launch_consolidated wrappers for the before/after (infra-ensure + agent-start + IP allocation), calling the shared helper.

The new backend/consolidated_util.py module holds only backend-neutral, transport-agnostic logic. All three backends import from it.

Implementation chunks

  1. (this PR) Dockerfile restructuring: rename current Dockerfile.infra content to Dockerfile.infra.fc; write new Dockerfile.infra as gateway+orchestrator base. Update Firecracker image-build references from Dockerfile.infraDockerfile.infra.fc.

  2. Add orchestrator process to gateway_init supervise tree.

  3. Collapse OrchestratorService to a single-container start; rename container from bot-bottle-orch-gateway/bot-bottle-orchestratorbot-bottle-infra; update image name constant.

  4. Extract backend/consolidated_util.py with poll_ca_cert, teardown_consolidated, and _provision_bottle; update all three consolidated_launch.py files to import from it.

  5. Update tests that reference the old container names or two-container startup sequence.

Open questions

None — the supervise-tree approach and shared Dockerfile layering were confirmed in issue #431.