fix(publish): bypass Docker Desktop proxy for loopback registries#13825
fix(publish): bypass Docker Desktop proxy for loopback registries#13825ptrdom wants to merge 3 commits into
Conversation
|
I have also manually tested this on MacOS (M1 Mac) with Docker Desktop, managed to reproduce the issue and confirmed that this PR fixes it. |
glours
left a comment
There was a problem hiding this comment.
Hello @ptrdom
Thanks for this contribution.
I’m concerned about the NO_PROXY part of this change.
The loopback bypass makes sense and seems like the right fix for the localhost registry case. However, this PR also makes the Compose OCI transport honor the local process NO_PROXY / no_proxy environment before routing through Docker Desktop’s proxy.
That changes the behavior introduced in 66c21c3, where OCI traffic was routed through Docker Desktop’s PAC-aware proxy so Desktop can own proxy decisions, especially for enterprise environments with managed or non-standard proxy configurations.
With this change, a broad local NO_PROXY value such as *, .corp, or a specific internal registry host could bypass Docker Desktop’s proxy entirely and connect directly. That could break enterprise proxy setups or bypass centrally managed proxy policy.
Can we keep the direct bypass limited to loopback targets only, and continue routing all other OCI registry traffic through Docker Desktop’s proxy?
There was a problem hiding this comment.
Pull request overview
This pull request fixes docker compose publish failures on Docker Desktop/Windows when targeting localhost or insecure registries by ensuring loopback/NO_PROXY destinations bypass Docker Desktop’s HTTP proxy and by correctly deriving the Windows named-pipe proxy endpoint.
Changes:
- Update the Docker Desktop proxy transport to use
httpproxy.Config.ProxyFuncfor loopback/NO_PROXYbypass and to only dial the Desktop proxy socket for proxied requests. - Fix Windows named-pipe proxy endpoint derivation to preserve the engine endpoint namespace (supports both backslash and forward-slash forms).
- Add/adjust unit tests covering loopback +
NO_PROXYproxy selection and Windows named-pipe endpoint derivation.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/desktop/proxy.go | Implements loopback/NO_PROXY bypass and correct proxy socket dialing logic; fixes Windows pipe endpoint derivation. |
| internal/desktop/proxy_test.go | Adds tests for proxy bypass behavior and Windows named-pipe endpoint derivation. |
| go.mod | Adds golang.org/x/net for httpproxy usage (can be avoided by using the stdlib package). |
|
|
||
| "github.com/moby/moby/client" | ||
| "github.com/sirupsen/logrus" | ||
| "golang.org/x/net/http/httpproxy" |
| go.yaml.in/yaml/v4 v4.0.0-rc.4 | ||
| golang.org/x/net v0.51.0 | ||
| golang.org/x/sync v0.20.0 | ||
| golang.org/x/sys v0.44.0 |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Address PR review (docker#13825): - Restrict the direct bypass to loopback targets (localhost, 127.0.0.0/8, ::1) only. The OCI transport no longer honors the local process NO_PROXY/no_proxy, so a broad value such as * or .corp can no longer bypass Docker Desktop's PAC-aware proxy and centrally managed enterprise proxy policy. All non-loopback registry traffic continues through Desktop's proxy, preserving the behavior from 66c21c3. - Drop the golang.org/x/net/http/httpproxy dependency now that NO_PROXY is no longer parsed; loopback detection is done inline. go mod tidy moves golang.org/x/net back to an indirect requirement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Domantas Petrauskas <dom.petrauskas@gmail.com>
|
@glours Thank you for a prompt review, I think your concern makes sense, I have pushed changes to address it. |
…stries compose publish routed all registry traffic through Docker Desktop's HTTP proxy, so publishing to a localhost/insecure registry failed on Windows with "proxyconnect tcp: open ./pipe/dockerHttpProxy: The system cannot find the path specified", while docker push/pull worked against the same registry. Two bugs in internal/desktop/proxy.go: - ProxyTransport forced every request through the DD proxy and its DialContext always dialed the proxy socket, so loopback targets could never connect directly. Select the proxy via httpproxy.Config.ProxyFunc, which exempts localhost/loopback and now also honors NO_PROXY; route only the sentinel proxy address to the DD socket and dial real targets directly otherwise. - The Windows named-pipe endpoint was hardcoded as npipe://./pipe/..., yielding the relative path ./pipe/dockerHttpProxy. Derive it from the engine endpoint, preserving the dialable npipe:////./pipe/ prefix. Fixes docker#13824 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Domantas Petrauskas <dom.petrauskas@gmail.com>
End-to-end validation against Docker Desktop 29.5.2 showed it reports its engine endpoint as `npipe://\.\pipe\docker_cli` (backslash namespace), not the forward-slash form assumed earlier. LastIndex(endpoint, "/") then matched the slash in "npipe://" and produced `npipe://dockerHttpProxy`, dropping the `\.\pipe\` namespace. Use LastIndexAny(endpoint, `/\`) so both the backslash form Docker Desktop actually reports and the forward-slash form resolve to a dialable pipe path. Verified the published artifact lands in a localhost registry and that the parent commit still reproduces the original "proxyconnect tcp: open ./pipe/dockerHttpProxy" failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Domantas Petrauskas <dom.petrauskas@gmail.com>
Address PR review (docker#13825): - Restrict the direct bypass to loopback targets (localhost, 127.0.0.0/8, ::1) only. The OCI transport no longer honors the local process NO_PROXY/no_proxy, so a broad value such as * or .corp can no longer bypass Docker Desktop's PAC-aware proxy and centrally managed enterprise proxy policy. All non-loopback registry traffic continues through Desktop's proxy, preserving the behavior from 66c21c3. - Drop the golang.org/x/net/http/httpproxy dependency now that NO_PROXY is no longer parsed; loopback detection is done inline. go mod tidy moves golang.org/x/net back to an indirect requirement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Domantas Petrauskas <dom.petrauskas@gmail.com>
26dfd83 to
7c92bbb
Compare
glours
left a comment
There was a problem hiding this comment.
I don’t see a blocker in the code after this update. One thing I’d still like to adjust before merging is the PR description/title: they still mention insecure registries broadly and the body still says NO_PROXY is honored, which is no longer the intended behavior. Could you update the wording to make it clear that only loopback targets bypass the Desktop proxy, while all other registry traffic stays routed through Docker Desktop?
|
Makes sense, updated, let me know if any further tweaks are needed. |
What I did
docker compose publishrouted all registry traffic through Docker Desktop's HTTP proxy. Publishing to a registry onlocalhosttherefore failed on Windows with:even though
docker push/docker pullworked against the same registry.Two bugs in
internal/desktop/proxy.go:ProxyTransportforced every request through the DD proxy and itsDialContextalways dialed the proxy socket, so loopback targets could never connect directly. Proxy selection now bypasses the proxy only for loopback targets (localhost,127.0.0.0/8,::1); all other registry traffic stays routed through Docker Desktop's PAC-aware proxy, so Desktop keeps ownership of proxy decisions (e.g. enterprise-managed proxies). The local processNO_PROXY/no_proxyis deliberately not honored, so a broad value such as*or.corpcannot bypass centrally managed proxy policy.DialContextroutes only the sentinel proxy address to the DD socket and dials loopback targets directly.npipe://./pipe/..., yielding the relative path./pipe/dockerHttpProxy. It is now derived from the engine endpoint, preserving its namespace. Docker Desktop reports the endpoint in the backslash formnpipe://\\.\pipe\docker_cli, so the derivation usesLastIndexAnyto handle both backslash and forward-slash forms.Result: publishing to
localhost:5000connects directly likedocker push, while every non-loopback registry still goes through the Docker Desktop proxy (now reachable on Windows).Related issue
Fixes #13824
How I tested
Validated end-to-end on Docker Desktop 29.5 / Windows 11 (the issue's environment) by building binaries from both the parent commit and this branch and running the issue's reproduction steps:
proxyconnect tcp: open ./pipe/dockerHttpProxy: The system cannot find the path specified.(exit 1).Automated coverage in
internal/desktop/proxy_test.go:TestDDProxyFunc_BypassesLoopbackOnly— loopback names/IPs bypass the proxy; all other hosts (including ones a localNO_PROXYwould match) stay routed through Docker Desktop.TestHTTPProxySocketEndpoint_WindowsNamedPipe— proxy pipe path keeps the engine endpoint's namespace for both backslash and forward-slash forms.🤖 Generated with Claude Code