Feature request
Add support for talking to the Docker daemon that ships inside WSL Containers (wslc) on Windows (Microsoft WSL 2.9+): a wslc:// transport, plus the small amount of lifecycle glue needed for published ports, host bind mounts and user-defined networks to actually work.
Motivation
WSL Containers runs dockerd inside a dedicated lightweight VM. Unlike Docker Desktop or Podman it exposes no Windows named pipe and no TCP port on the host — the only host-visible channel to the daemon is a stdio bridge:
wslc.exe system session run docker system dial-stdio
This is the same shape as the existing ssh:// transport (a child process whose stdin/stdout are the daemon socket). Today docker-java (and Testcontainers on top of it) has no way to reach a wslc-only daemon.
Proposed change
1. Transport (docker-java-transport + -transport-httpclient5) — mirror unix/npipe, dependency-free:
- new
WslcSocket extends Socket, streams backed by the wslc … dial-stdio child process (this module is JNA-only / no SLF4J, so it uses java.util.logging — no new dependency);
- add
case "wslc": to the scheme switch in ApacheDockerHttpClientImpl and a new WslcSocket(dockerHostPath) branch. Hijacked exec/attach/log streams work for free (they run at the HttpClient5 socket level).
2. Lifecycle integration (docker-java-core) — wslc's Windows integration (the 127.0.0.1 port relay and Windows-path bind mounts) is wired by the wslc control plane only when a container is created/started through the native wslc CLI, not through the Docker API on the same daemon (see microsoft/WSL #40957). A container created via the Docker API reaches the same dockerd but its published ports aren't reachable from Windows and host binds are rejected.
To bridge that, a WslcLifecycleDockerHttpClient (a DockerHttpClient decorator, jackson-only, no new dependency) reconciles just the affected calls with the wslc CLI and delegates everything else unchanged:
POST /containers/create → wslc create
POST /containers/{id}/start → wslc start (this is what wires relay + bind)
POST /networks/create → wslc network create (wslc --network only sees wslc-created networks; the id→name is resolved via a Docker-API inspect)
GET /containers/{id}/json → the daemon's inspect with NetworkSettings.Ports overridden from wslc list (for a randomly published port the daemon records the create-time port but the start relay binds a different one)
Activation is transparent: DockerClientImpl.getInstance(config, httpClient) wraps the client in this decorator only when the host scheme is wslc. So it never affects unix/npipe/tcp users, and every docker-java consumer picks it up with no extra wiring.
3. Auto-detection (docker-java-core / DefaultDockerClientConfig)
— when DOCKER_HOST is unset, fall back to wslc://localhost on Windows only if the //./pipe/docker_engine pipe is absent AND wslc is available (wslc version exit 0, overridable via WSLC_EXECUTABLE) — so existing Docker Desktop / Podman setups keep winning.
Using it with Testcontainers
Testcontainers shades docker-java-core but consumes docker-java-api / -transport-zerodep as normal dependencies. So with just this PR:
- set
DOCKER_HOST=wslc://localhost explicitly, and
- rebuild Testcontainers against the updated (shaded) docker-java so the new
getInstance activation is re-shaded into its jar (a plain version bump is not enough — core is shaded).
That combination is verified working end-to-end: a real Testcontainers/Cucumber integration-test suite goes fully green over wslc (containers create/start via the CLI, ports reachable from Windows, custom networks resolved).
The one piece that can't live in docker-java is automatic host selection when DOCKER_HOST is unset — that requires a class implementing Testcontainers' DockerClientProviderStrategy, which docker-java can't provide without depending on Testcontainers. I'll open a separate PR against testcontainers-java to add that automatic fallback to wslc:// when it's the only Docker environment present on Windows, so users won't need to set DOCKER_HOST at all.
Feature request
Add support for talking to the Docker daemon that ships inside WSL Containers (wslc) on Windows (Microsoft WSL 2.9+): a
wslc://transport, plus the small amount of lifecycle glue needed for published ports, host bind mounts and user-defined networks to actually work.Motivation
WSL Containers runs
dockerdinside a dedicated lightweight VM. Unlike Docker Desktop or Podman it exposes no Windows named pipe and no TCP port on the host — the only host-visible channel to the daemon is a stdio bridge:wslc.exe system session run docker system dial-stdioThis is the same shape as the existing
ssh://transport (a child process whose stdin/stdout are the daemon socket). Today docker-java (and Testcontainers on top of it) has no way to reach a wslc-only daemon.Proposed change
1. Transport (
docker-java-transport+-transport-httpclient5) — mirrorunix/npipe, dependency-free:WslcSocket extends Socket, streams backed by thewslc … dial-stdiochild process (this module is JNA-only / no SLF4J, so it usesjava.util.logging— no new dependency);case "wslc":to the schemeswitchinApacheDockerHttpClientImpland anew WslcSocket(dockerHostPath)branch. Hijacked exec/attach/log streams work for free (they run at the HttpClient5 socket level).2. Lifecycle integration (
docker-java-core) — wslc's Windows integration (the127.0.0.1port relay and Windows-path bind mounts) is wired by the wslc control plane only when a container is created/started through the nativewslcCLI, not through the Docker API on the same daemon (see microsoft/WSL #40957). A container created via the Docker API reaches the same dockerd but its published ports aren't reachable from Windows and host binds are rejected.To bridge that, a
WslcLifecycleDockerHttpClient(aDockerHttpClientdecorator, jackson-only, no new dependency) reconciles just the affected calls with thewslcCLI and delegates everything else unchanged:POST /containers/create→wslc createPOST /containers/{id}/start→wslc start(this is what wires relay + bind)POST /networks/create→wslc network create(wslc --networkonly sees wslc-created networks; the id→name is resolved via a Docker-API inspect)GET /containers/{id}/json→ the daemon's inspect withNetworkSettings.Portsoverridden fromwslc list(for a randomly published port the daemon records the create-time port but the start relay binds a different one)Activation is transparent:
DockerClientImpl.getInstance(config, httpClient)wraps the client in this decorator only when the host scheme iswslc. So it never affects unix/npipe/tcp users, and every docker-java consumer picks it up with no extra wiring.3. Auto-detection (
docker-java-core/DefaultDockerClientConfig)— when
DOCKER_HOSTis unset, fall back towslc://localhoston Windows only if the//./pipe/docker_enginepipe is absent ANDwslcis available (wslc versionexit 0, overridable viaWSLC_EXECUTABLE) — so existing Docker Desktop / Podman setups keep winning.Using it with Testcontainers
Testcontainers shades
docker-java-corebut consumesdocker-java-api/-transport-zerodepas normal dependencies. So with just this PR:DOCKER_HOST=wslc://localhostexplicitly, andgetInstanceactivation is re-shaded into its jar (a plain version bump is not enough — core is shaded).That combination is verified working end-to-end: a real Testcontainers/Cucumber integration-test suite goes fully green over wslc (containers create/start via the CLI, ports reachable from Windows, custom networks resolved).
The one piece that can't live in docker-java is automatic host selection when
DOCKER_HOSTis unset — that requires a class implementing Testcontainers'DockerClientProviderStrategy, which docker-java can't provide without depending on Testcontainers. I'll open a separate PR against testcontainers-java to add that automatic fallback towslc://when it's the only Docker environment present on Windows, so users won't need to setDOCKER_HOSTat all.