fix(network): create user-defined egress bridge for pipelock sidecar

Docker's legacy `bridge` network has no embedded DNS resolver — only
user-defined bridges do — so attaching the pipelock sidecar to `bridge`
made it unable to resolve `api.anthropic.com` and dead-ended Claude Code
traffic. Add `network_create_egress`, refactored around a shared
`_network_create_with_prefix` helper, and wire it through `pipelock_start`
and `cli.sh` so the sidecar straddles the agent's --internal network and
a per-agent user-defined egress bridge instead. The agent container
itself still attaches to the internal network only.

Assisted-by: Claude Code
This commit is contained in:
2026-05-08 01:16:46 -04:00
parent 8d2110ba06
commit 55bb230969
3 changed files with 130 additions and 54 deletions
+19 -7
View File
@@ -513,21 +513,30 @@ cmd_start() {
build_image_with_cwd "$DERIVED_IMAGE" "$IMAGE" "$USER_CWD"
fi
# PRD 0001: per-agent egress topology. Create the --internal Docker
# network and start the pipelock sidecar on it BEFORE the agent
# container, so the agent's HTTPS_PROXY target exists at the moment
# the agent boots.
# PRD 0001: per-agent egress topology. Create the two Docker
# networks the sidecar needs, then start the pipelock sidecar on
# them BEFORE the agent container, so the agent's HTTPS_PROXY target
# exists at the moment the agent boots.
#
# The agent container itself stays on INTERNAL_NETWORK only — only
# the sidecar straddles both. The egress network is the sidecar's
# path to the upstream internet (must be a user-defined bridge so
# Docker's embedded DNS resolves api.anthropic.com et al.; the
# legacy `bridge` network has no embedded DNS and is the wrong
# answer here — see lib/network.sh).
#
# Not declared local: needed by cleanup_all after cmd_start returns
# (same reason as MANIFEST_FILE / STAGE_DIR / CONTAINER above).
INTERNAL_NETWORK=""
EGRESS_NETWORK=""
PIPELOCK_CONTAINER=""
INTERNAL_NETWORK="$(network_create_internal "$SLUG")"
PIPELOCK_CONTAINER="$(pipelock_start "$SLUG" "$INTERNAL_NETWORK" "$STAGE_DIR" "$PIPELOCK_YAML_FILENAME")"
EGRESS_NETWORK="$(network_create_egress "$SLUG")"
PIPELOCK_CONTAINER="$(pipelock_start "$SLUG" "$INTERNAL_NETWORK" "$EGRESS_NETWORK" "$STAGE_DIR" "$PIPELOCK_YAML_FILENAME")"
# Cleanup container on exit too. Compose with stage cleanup.
# Order matters: sidecar first, then internal network — docker
# refuses to remove a network with attached containers.
# Order matters: sidecar first, then networks — docker refuses to
# remove a network with attached containers.
cleanup_all() {
if container_exists "$CONTAINER"; then
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
@@ -538,6 +547,9 @@ cmd_start() {
if [ -n "${INTERNAL_NETWORK:-}" ]; then
network_remove "$INTERNAL_NETWORK"
fi
if [ -n "${EGRESS_NETWORK:-}" ]; then
network_remove "$EGRESS_NETWORK"
fi
cleanup_stage
}
# Replaces the cleanup_stage EXIT trap above; cleanup_all calls cleanup_stage internally.