On a Windows node running the default NAT container network, a job container cannot reach its own dind Docker API. Every docker command inside a Windows job fails:
Post "http://10.88.0.1:51064/v1.47/auth": dial tcp 10.88.0.1:51064: i/o timeout
This has blocked build-images.yml's Windows job since at least 2026-07-24 — the last successful Windows CI image build was 2026-07-12. Reproduced today on mfl-win-amd64-101 running v0.2.2 (runs 31526577360 and 31976688505, different ephemeral ports, identical failure).
Cause
On Windows the dind Docker API is exposed over TCP on the container network's gateway (10.88.0.1), because a bind-mounted unix socket is not available there. Reaching it is an inbound connection to the host.
The Windows host firewall is enabled on all three profiles with the effective inbound default of block:
Name Enabled DefaultInboundAction
Domain True NotConfigured
Private True NotConfigured
Public True NotConfigured
and the only ephemerd rule present is unrelated:
DisplayName Direction Action Enabled
ephemerd-metrics-9090 Inbound Allow True
windowsNetworking.openHostPort returns early unless cfg.L2BridgeEgress is set:
func (w *windowsNetworking) openHostPort(port int, containerIP string) error {
if !w.cfg.L2BridgeEgress || w.plan == nil || w.plan.HostIP == "" {
return nil
}
...
So on a NAT node nothing ever opens the port, and the host silently drops the container's SYN. i/o timeout rather than connection refused is consistent with a drop, not a closed port.
This node has no [network] section at all — L2Bridge was never enabled here — so it takes the early return on every job.
Blast radius
Any Windows job that talks to the Docker API: docker build, docker login, docker push, testcontainers, anything using dind. Jobs that never touch Docker are unaffected, which is why this stayed invisible — the ephemerd release build passes on this node because it only runs mage.
Suggested fix
openHostPort/closeHostPort should open a scoped inbound allow on the NAT path too, not only under L2Bridge. Under NAT the natural scope is the container's own address within the NAT subnet, keeping the same per-container /32 scoping #152 established — a pool-wide allow would reintroduce exactly the cross-job dind takeover that issue closed.
Worth checking as part of this: whether the NAT path previously had its own inbound rule installation that was removed. #144 stripped a large amount of NAT-era firewall code (installNetshFirewallRules, hostFirewallRules, and others) on the grounds that it was egress-enforcement theatre, which was correct for egress — but if any of it was doing legitimate inbound allows for dind, that would be a regression introduced there. Note the timeline does not obviously fit: the Windows image build was already failing on 2026-07-24, well before #144 merged, so there may be two separate causes.
Why this matters now
It blocks rebuilding the Windows CI runner image, which in turn blocks #161 (seeding the Go tool cache so actions/setup-go stops timing out). So this is on the critical path for the Windows setup-go failures too.
On a Windows node running the default NAT container network, a job container cannot reach its own dind Docker API. Every
dockercommand inside a Windows job fails:This has blocked
build-images.yml's Windows job since at least 2026-07-24 — the last successful Windows CI image build was 2026-07-12. Reproduced today onmfl-win-amd64-101running v0.2.2 (runs 31526577360 and 31976688505, different ephemeral ports, identical failure).Cause
On Windows the dind Docker API is exposed over TCP on the container network's gateway (
10.88.0.1), because a bind-mounted unix socket is not available there. Reaching it is an inbound connection to the host.The Windows host firewall is enabled on all three profiles with the effective inbound default of block:
and the only ephemerd rule present is unrelated:
windowsNetworking.openHostPortreturns early unlesscfg.L2BridgeEgressis set:So on a NAT node nothing ever opens the port, and the host silently drops the container's SYN.
i/o timeoutrather thanconnection refusedis consistent with a drop, not a closed port.This node has no
[network]section at all — L2Bridge was never enabled here — so it takes the early return on every job.Blast radius
Any Windows job that talks to the Docker API:
docker build,docker login,docker push, testcontainers, anything using dind. Jobs that never touch Docker are unaffected, which is why this stayed invisible — the ephemerd release build passes on this node because it only runsmage.Suggested fix
openHostPort/closeHostPortshould open a scoped inbound allow on the NAT path too, not only under L2Bridge. Under NAT the natural scope is the container's own address within the NAT subnet, keeping the same per-container/32scoping #152 established — a pool-wide allow would reintroduce exactly the cross-job dind takeover that issue closed.Worth checking as part of this: whether the NAT path previously had its own inbound rule installation that was removed. #144 stripped a large amount of NAT-era firewall code (
installNetshFirewallRules,hostFirewallRules, and others) on the grounds that it was egress-enforcement theatre, which was correct for egress — but if any of it was doing legitimate inbound allows for dind, that would be a regression introduced there. Note the timeline does not obviously fit: the Windows image build was already failing on 2026-07-24, well before #144 merged, so there may be two separate causes.Why this matters now
It blocks rebuilding the Windows CI runner image, which in turn blocks #161 (seeding the Go tool cache so
actions/setup-gostops timing out). So this is on the critical path for the Windowssetup-gofailures too.