Repository navigation
[Windows/WSL2] com.docker.backend keeps host TCP sockets in "Bound" state after container connections close, never released (reproduces on 4.96.0) #769
Description
Bug report
Bug description
On Windows with the WSL2 backend, com.docker.backend keeps piling up TCP sockets in the Bound state on the Windows host: 0.0.0.0:<ephemeral port>, no remote endpoint, all owned by one com.docker.backend process. Each comes from a container connection that has already finished. They aren't released until Docker Desktop restarts. Each one permanently takes a port from Windows' ephemeral range (49152–65535), which every process on the host shares.
Expected: after a container's connection closes, com.docker.backend releases its host-side socket.
Actual: some connections leave a Bound socket behind for days, until Docker Desktop restarts.
On an earlier version (4.81.0) with heavier traffic, the same backend reached ~14,300 held sockets after 15 days, leaving ~1,400 free ephemeral ports. That caused host-wide intermittent connection failures: DNS timeouts and TLS "unexpected EOF" in containers, plus connect timeouts from native Windows processes. ICMP to the gateway kept working. Restarting Docker Desktop released everything at once.
Evidence on 4.96.0
Docker Desktop was updated to 4.96.0 (backend restarted with a clean count). After 47.4 hours:
| Measure | Value |
|---|---|
com.docker.backend sockets in Bound |
120 |
| Released over the period | 1 of 121 |
| Age of the held sockets | 22 older than 36 h, 32 aged 24–36 h, 28 aged 12–24 h, 36 aged 1–12 h, 2 under 1 h |
| Leak rate | ~60 per day |
The leaks happen in bursts that line up, to the second, with scheduled jobs in two unrelated containerized apps:
| Source | Sockets held per run |
|---|---|
| App 1 (qbit_manage, 4 instances, one round every 4 h) | 6 per 4-hour cycle, on all 11 cycles over 47 h (4 at the main round + 2 at one instance whose schedule shifted) |
| App 2 (Kometa, one run daily) | 9 and 14 on the two daily runs |
Background is ~0: with neither job running for 20 minutes, the Bound total stayed flat (605 → 605, measured on 4.81). Over the same 20 minutes com.docker.backend carried 417 other connections without leaking: BitTorrent peers, HTTPS to the internet, and HTTP to services on the host.
Which connections leak
I triggered each App 1 instance on its own (on 4.81; the per-run counts are the same on 4.96) and recorded what each leaked socket had been connected to:
| Instance | Held sockets per run | Leaked socket's prior connection |
|---|---|---|
| A | 1 | an external HTTPS API on :443, Cloudflare-fronted (matched to the app's notification service by DNS and by timing) |
| B | 1 | (same app flow) |
| C | 2 | the same external API, plus 127.0.0.1:8088, which is how the backend reaches a native Windows HTTP service the container calls at host.docker.internal:8088 |
| D | 2 | (same configuration as C) |
Most connections from the same containers don't leak, including other connections to the same API's other addresses. In one test, a single unauthenticated request from a container to the native service at host.docker.internal:8088 left the backend's host-side socket in CLOSE_WAIT for ~2 minutes. It was then still held as Bound 13+ minutes later, when the check ended.
Reproduction steps
- Run a containerized client that periodically calls (a) an external HTTPS API and (b) a native Windows HTTP service through
host.docker.internal:<port>. I saw this with a Pythonrequests/qbittorrent-apiclient. - On the Windows host, record the held sockets before and after each client run:
$ids = (Get-Process com.docker.backend).Id (Get-NetTCPConnection -State Bound | Where-Object { $_.OwningProcess -in $ids }).Count # age profile: anything hours old is a leak; momentary or minutes-long Bound states are normal Get-NetTCPConnection -State Bound | Where-Object { $_.OwningProcess -in $ids } | Group-Object { $_.CreationTime.ToString('MM-dd HH') }
- Each run leaves 1–2 sockets per client session that stay Bound for days.
Minimal standalone repro: not yet found. One-shot curl, Python urllib and requests calls from a container to the same external endpoint didn't leak. A throwaway nginx target produced Bound sockets that were all released within ~5–9 minutes. Only count sockets that stay Bound for hours.
Environment notes
- WSL2 backend, WSL 2.5.7.0, Windows 10 Home 22H2 (build 19045.6456)
- Default Docker Desktop network settings; no system proxy, and containers have no proxy environment
- Containers on the default bridge and user-defined networks; ports published as
0.0.0.0:<port>:<port> - Related, not duplicates: [macOS] Built-in HTTP proxy leaks one backend socket per plain-HTTP container request (upstream stays CLOSE_WAIT/CLOSED); backend crashes with "too many open files" after ~92k requests #752 (macOS;
com.docker.backendnever closing fds after CLOSE_WAIT → CLOSED, via the HTTP-proxy path, which isn't used here) and com.docker.sailor leaks host UDP endpoints until the ephemeral port range is exhausted, breaking DNS host-wide (Windows, gvisor) #633 (UDP leak incom.docker.sailor, fixed in 4.91; that component doesn't run here)
Impact
Exhaustion is slow at this host's current traffic (~60 sockets a day). Before I identified the trigger and moved the busiest client off the host.docker.internal path, though, the leak used up nearly the whole ephemeral range in about two weeks and broke outbound connectivity for every process on the host. Restarting Docker Desktop is the only remedy.
Platform
Windows
Version information
Client:
Version: 29.9.1
API version: 1.56
Go version: go1.26.9
Git commit: dfc520d
Built: Wed Oct 14 18:34:24 2026
OS/Arch: windows/amd64
Context: desktop-linux
Server: Docker Desktop 4.96.0 (243774)
Engine:
Version: 29.9.1
API version: 1.56 (minimum version 1.40)
Go version: go1.26.9
Git commit: b26fb77
Built: Wed Oct 14 18:32:24 2026
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: v2.4.2
GitCommit: b7a25261e029d68978bbf290df0a62fc819b3c51
runc:
Version: 1.5.2
GitCommit: v1.5.2-0-g29dd3dc2
docker-init:
Version: 0.19.0
GitCommit: de40ad0
Client:
Version: 29.9.1
Context: desktop-linux
Debug Mode: false
Plugins:
agent: Docker AI Agent Runner (Docker Inc.)
Version: v1.149.0
Path: C:\Program Files\Docker\cli-plugins\docker-agent.exe
ai: Docker AI Agent - Ask Gordon (Docker Inc.)
Version: v1.31.0
Path: C:\Program Files\Docker\cli-plugins\docker-ai.exe
buildx: Docker Buildx (Docker Inc.)
Version: v0.38.0
Path: C:\Program Files\Docker\cli-plugins\docker-buildx.exe
compose: Docker Compose (Docker Inc.)
Version: v5.6.0
Path: C:\Program Files\Docker\cli-plugins\docker-compose.exe
debug: Get a shell into any image or container (Docker Inc.)
Version: 0.0.47
Path: C:\Program Files\Docker\cli-plugins\docker-debug.exe
desktop: Docker Desktop commands (Docker Inc.)
Version: 4.96.0
Path: C:\Program Files\Docker\cli-plugins\docker-desktop.exe
dhi: CLI for managing Docker Hardened Images (Docker Inc.)
Version: v0.0.7
Path: C:\Program Files\Docker\cli-plugins\docker-dhi.exe
extension: Manages Docker extensions (Docker Inc.)
Version: v0.2.31
Path: C:\Program Files\Docker\cli-plugins\docker-extension.exe
init: Creates Docker-related starter files for your project (Docker Inc.)
Version: v1.4.0
Path: C:\Program Files\Docker\cli-plugins\docker-init.exe
mcp: Docker MCP Plugin (Docker Inc.)
Version: v0.44.1
Path: C:\Program Files\Docker\cli-plugins\docker-mcp.exe
model: Docker Model Runner (Docker Inc.)
Version: v1.2.6
Path: C:\Program Files\Docker\cli-plugins\docker-model.exe
offload: Docker Offload (Docker Inc.)
Version: v0.7.4
Path: C:\Program Files\Docker\cli-plugins\docker-offload.exe
pass: Docker Pass Secrets Manager Plugin (beta) (Docker Inc.)
Version: v0.3.0
Path: C:\Program Files\Docker\cli-plugins\docker-pass.exe
sandbox: "docker sandbox" is deprecated, use Docker Sandboxes instead (Docker Inc.)
Version: v0.13.0
Path: C:\Program Files\Docker\cli-plugins\docker-sandbox.exe
scout: Docker Scout (Docker Inc.)
Version: v1.26.0
Path: C:\Program Files\Docker\cli-plugins\docker-scout.exe
Server:
Containers: 18
Running: 16
Paused: 0
Stopped: 2
Images: 27
Server Version: 29.9.1
Storage Driver: overlayfs
driver-type: io.containerd.snapshotter.v1
Logging Driver: local
Cgroup Driver: cgroupfs
Cgroup Version: 2
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
CDI spec directories:
/etc/cdi
/var/run/cdi
Discovered Devices:
cdi: docker.com/gpu=webgpu
Swarm: inactive
Runtimes: io.containerd.runc.v2 nvidia runc
Default Runtime: runc
Init Binary: docker-init
containerd version: b7a25261e029d68978bbf290df0a62fc819b3c51
runc version: v1.5.2-0-g29dd3dc2
init version: de40ad0
Security Options:
seccomp
Profile: builtin
cgroupns
Kernel Version: 6.6.87.1-microsoft-standard-WSL2
Operating System: Docker Desktop
OSType: linux
Architecture: x86_64
CPUs: 8
Total Memory: 15.59GiB
Name: docker-desktop
ID: <redacted>
Docker Root Dir: /var/lib/docker
Debug Mode: false
HTTP Proxy: http.docker.internal:3128
HTTPS Proxy: http.docker.internal:3128
No Proxy: hubproxy.docker.internal
Labels:
com.docker.desktop.address=npipe://\\.\pipe\docker_cli
Experimental: false
Insecure Registries:
hubproxy.docker.internal:5555
127.0.0.0/8
::1/128
Live Restore Enabled: false
Firewall Backend: iptablesDiagnostics ID
C8E50518-8ACE-49CD-B98F-A902B3569482/20261021152506
Activity