Skip to content

fix(publish): bypass Docker Desktop proxy for loopback registries#13825

Open
ptrdom wants to merge 3 commits into
docker:mainfrom
ptrdom:fix/publish-localhost-proxy-bypass
Open

fix(publish): bypass Docker Desktop proxy for loopback registries#13825
ptrdom wants to merge 3 commits into
docker:mainfrom
ptrdom:fix/publish-localhost-proxy-bypass

Conversation

@ptrdom

@ptrdom ptrdom commented Jun 7, 2026

Copy link
Copy Markdown

What I did

docker compose publish routed all registry traffic through Docker Desktop's HTTP proxy. Publishing to a registry on localhost therefore failed on Windows with:

proxyconnect tcp: open ./pipe/dockerHttpProxy: The system cannot find the path specified.

even though docker push/docker pull worked against the same registry.

Two bugs in internal/desktop/proxy.go:

  1. No loopback bypass. ProxyTransport forced every request through the DD proxy and its DialContext always 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 process NO_PROXY/no_proxy is deliberately not honored, so a broad value such as * or .corp cannot bypass centrally managed proxy policy. DialContext routes only the sentinel proxy address to the DD socket and dials loopback targets directly.
  2. Malformed Windows pipe path. The proxy named-pipe endpoint was hardcoded as 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 form npipe://\\.\pipe\docker_cli, so the derivation uses LastIndexAny to handle both backslash and forward-slash forms.

Result: publishing to localhost:5000 connects directly like docker 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:

# start a plain-HTTP localhost registry
docker run -d -p 5000:5000 --name registry registry:3

# minimal compose.yaml referencing any pushable image, then publish
# (spike-app is the placeholder image name from #13824's repro steps;
#  --insecure-registry tells the OCI resolver to use plain HTTP)
docker compose publish localhost:5000/spike-app:1 --insecure-registry -y
  • Parent commit: reproduces the issue verbatim — proxyconnect tcp: open ./pipe/dockerHttpProxy: The system cannot find the path specified. (exit 1).
  • This branch: publishes successfully (exit 0); the artifact lands in the localhost registry.

Automated coverage in internal/desktop/proxy_test.go:

  • TestDDProxyFunc_BypassesLoopbackOnly — loopback names/IPs bypass the proxy; all other hosts (including ones a local NO_PROXY would 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

@ptrdom ptrdom requested a review from a team as a code owner June 7, 2026 13:05
@ptrdom ptrdom requested review from glours and ndeloof June 7, 2026 13:05
@ptrdom

ptrdom commented Jun 7, 2026

Copy link
Copy Markdown
Author

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 glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ProxyFunc for loopback/NO_PROXY bypass 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_PROXY proxy 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).

Comment thread internal/desktop/proxy.go Outdated

"github.com/moby/moby/client"
"github.com/sirupsen/logrus"
"golang.org/x/net/http/httpproxy"
Comment thread go.mod
Comment on lines 55 to 58
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

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.83333% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/desktop/proxy.go 70.83% 5 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

ptrdom added a commit to ptrdom/compose that referenced this pull request Jun 9, 2026
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>
@ptrdom

ptrdom commented Jun 9, 2026

Copy link
Copy Markdown
Author

@glours Thank you for a prompt review, I think your concern makes sense, I have pushed changes to address it.

ptrdom and others added 3 commits June 9, 2026 17:05
…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>
@ptrdom ptrdom force-pushed the fix/publish-localhost-proxy-bypass branch from 26dfd83 to 7c92bbb Compare June 9, 2026 14:06
@ptrdom ptrdom requested a review from glours June 9, 2026 14:06

@glours glours left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@ptrdom ptrdom changed the title fix(publish): bypass Docker Desktop proxy for localhost/insecure registries fix(publish): bypass Docker Desktop proxy for loopback registries Jun 9, 2026
@ptrdom

ptrdom commented Jun 9, 2026

Copy link
Copy Markdown
Author

Makes sense, updated, let me know if any further tweaks are needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docker compose publish ignores insecure/localhost registry and routes through proxy (proxyconnect ./pipe/dockerHttpProxy)

3 participants