You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #20 / PR #93. The brokered Docker socket (--dind) works end-to-end in standard mode (verified against the HubX Questions integration tests: 463/463 passing in ~88 seconds, 0 broker blocks). It does not work yet when combined with --enable-airlock.
What's wrong
Two stacked problems show up the moment you add --enable-airlock to a --dind session:
HTTP_PROXY routing. The airlock compose template sets HTTP_PROXY=http://proxy:58080 and HTTPS_PROXY=http://proxy:58080 on the workload container so all outbound HTTP traffic gets filtered. HTTP clients (including Docker.DotNet's Microsoft.Net.Http.Client.ManagedHandler used by Testcontainers .NET) dutifully respect that and route every request — including the one to host.docker.internal:NNN where the broker is listening — through the airlock proxy. The airlock proxy then rejects it with Host not allowed because host.docker.internal isn't in the allowlist.
Already fixed in PR feat: Add brokered Docker socket (--dind, beta) for #20 #93: when --dind is enabled in airlock mode, AirlockRunner now also emits NO_PROXY=host.docker.internal,localhost,127.0.0.1 so the HTTP client bypasses the airlock proxy for broker traffic.
Internal-only network with no host route. Even with NO_PROXY in place, the airlock network in compose is internal: true. That deliberately strips any route from the workload container to the host or external network. Trying to reach host.docker.internal:NNN directly fails with dial tcp 0.250.250.254:NNN: connect: network is unreachable. The host-process broker is on the wrong side of the airlock — there's no path to it from inside the airlock'd container.
Why standard mode works
In standard mode the workload container is launched on the default bridge network with --add-host host.docker.internal:host-gateway. host-gateway resolves to the docker bridge gateway and is reachable from the container. The host process is bound to 127.0.0.1:NNN and (on macOS / Windows) Docker Desktop / OrbStack route host.docker.internal:NNN to the host loopback. End-to-end works — verified against the full hubx integration suite.
Proposed fix: sidecar broker container
To make --dind + airlock work, the broker has to live on the airlock network rather than on the host. Specifically:
Build a small Docker image (copilot_here:broker?) that runs the existing DockerSocketBroker C# code as its entrypoint. The image should be tiny — ASP.NET-free, native AOT, basically the same broker logic that lives in copilot_here.exe today but packaged to run standalone.
The broker container mounts the host's /var/run/docker.sock (or named pipe on Windows) and exposes its own TCP port internally.
When --dind is active and airlock is on, AirlockRunner adds the broker container as a service in the compose project. Both the broker and the workload sit on the airlock network. The workload container's DOCKER_HOST becomes tcp://copilot-broker:NNN (service name resolution) instead of tcp://host.docker.internal:NNN.
Standard mode keeps using the host-process broker (no need to spin up a sidecar container). Or we move standard mode to the sidecar container too for consistency — that's a follow-up decision.
Alternative considered: use the existing airlock proxy as a TCP forwarder
I looked at whether the existing secure-proxy (Rust binary in /proxy/) could also forward TCP traffic to the broker. It can't — it only handles HTTP CONNECT for HTTPS proxying, not arbitrary TCP. Adding raw TCP forwarding to the airlock proxy is doable but conflates two different responsibilities (HTTP allowlist vs raw TCP plumbing). The sidecar broker is cleaner.
I have a full smoke runner at /tmp/dind-test/AirlockSmokeRunner (local only, not committed) that exercises the airlock + DinD path end-to-end against the HubX integration tests via reflection into AirlockRunner.GenerateComposeFile. Once the sidecar broker exists, that runner can be adapted into a permanent integration test.
Follow-up to #20 / PR #93. The brokered Docker socket (
--dind) works end-to-end in standard mode (verified against the HubX Questions integration tests: 463/463 passing in ~88 seconds, 0 broker blocks). It does not work yet when combined with--enable-airlock.What's wrong
Two stacked problems show up the moment you add
--enable-airlockto a--dindsession:HTTP_PROXY routing. The airlock compose template sets
HTTP_PROXY=http://proxy:58080andHTTPS_PROXY=http://proxy:58080on the workload container so all outbound HTTP traffic gets filtered. HTTP clients (including Docker.DotNet'sMicrosoft.Net.Http.Client.ManagedHandlerused by Testcontainers .NET) dutifully respect that and route every request — including the one tohost.docker.internal:NNNwhere the broker is listening — through the airlock proxy. The airlock proxy then rejects it withHost not allowedbecausehost.docker.internalisn't in the allowlist.Already fixed in PR feat: Add brokered Docker socket (--dind, beta) for #20 #93: when
--dindis enabled in airlock mode,AirlockRunnernow also emitsNO_PROXY=host.docker.internal,localhost,127.0.0.1so the HTTP client bypasses the airlock proxy for broker traffic.Internal-only network with no host route. Even with
NO_PROXYin place, the airlock network in compose isinternal: true. That deliberately strips any route from the workload container to the host or external network. Trying to reachhost.docker.internal:NNNdirectly fails withdial tcp 0.250.250.254:NNN: connect: network is unreachable. The host-process broker is on the wrong side of the airlock — there's no path to it from inside the airlock'd container.Why standard mode works
In standard mode the workload container is launched on the default bridge network with
--add-host host.docker.internal:host-gateway. host-gateway resolves to the docker bridge gateway and is reachable from the container. The host process is bound to127.0.0.1:NNNand (on macOS / Windows) Docker Desktop / OrbStack routehost.docker.internal:NNNto the host loopback. End-to-end works — verified against the full hubx integration suite.Proposed fix: sidecar broker container
To make
--dind+ airlock work, the broker has to live on the airlock network rather than on the host. Specifically:Build a small Docker image (
copilot_here:broker?) that runs the existingDockerSocketBrokerC# code as its entrypoint. The image should be tiny — ASP.NET-free, native AOT, basically the same broker logic that lives incopilot_here.exetoday but packaged to run standalone.The broker container mounts the host's
/var/run/docker.sock(or named pipe on Windows) and exposes its own TCP port internally.When
--dindis active and airlock is on,AirlockRunneradds the broker container as a service in the compose project. Both the broker and the workload sit on theairlocknetwork. The workload container'sDOCKER_HOSTbecomestcp://copilot-broker:NNN(service name resolution) instead oftcp://host.docker.internal:NNN.Standard mode keeps using the host-process broker (no need to spin up a sidecar container). Or we move standard mode to the sidecar container too for consistency — that's a follow-up decision.
Alternative considered: use the existing airlock proxy as a TCP forwarder
I looked at whether the existing secure-proxy (Rust binary in
/proxy/) could also forward TCP traffic to the broker. It can't — it only handles HTTP CONNECT for HTTPS proxying, not arbitrary TCP. Adding raw TCP forwarding to the airlock proxy is doable but conflates two different responsibilities (HTTP allowlist vs raw TCP plumbing). The sidecar broker is cleaner.Out of scope
POST /containers/create(privileged, host bind mounts, host network/pid/ipc) — already filed as Phase 2: Docker broker body inspection (privileged, host bind mounts, host network/pid/ipc) #94, that's the Phase 2 work.--dindregressions — covered by PR feat: Add brokered Docker socket (--dind, beta) for #20 #93 and 4 broker bug fixes (see commit1e3d929).Testing notes
I have a full smoke runner at
/tmp/dind-test/AirlockSmokeRunner(local only, not committed) that exercises the airlock + DinD path end-to-end against the HubX integration tests via reflection intoAirlockRunner.GenerateComposeFile. Once the sidecar broker exists, that runner can be adapted into a permanent integration test.Refs