Fixes the WSL2 setup instructions in the docker-provider-via-wsl
tutorial and two underlying bugs that broke workspaces using an
ssh:// Docker daemon on Windows.
Mount-path translation (EnsurePath):
EnsurePath only converted Windows drive paths to their WSL /mnt/<drive>
form when DOCKER_HOST was tcp://. The documented WSL setup uses an
ssh:// DOCKER_HOST pointing at a daemon inside the WSL2 VM, so
workspace mounts were passed through as raw Windows paths the WSL
daemon could not resolve, causing build/tar failures before reaching
the editor SSH stage.
- Generalized remote-daemon detection to any non-local DOCKER_HOST
(not unix:// or npipe://) via the new docker.RemoteDockerHost
helper, covering both tcp:// and ssh:// endpoints.
- Extracted path conversion into a testable windowsToWSLPath helper.
- Added TestWSLPathConversion, TestIsRemoteDockerHost,
TestIsRemoteDockerHostEnv, and a non-Windows no-op guard.
Agent delivery routing never checked the real DOCKER_HOST:
NewAgentDelivery already had an IsRemoteDocker field that takes
priority when choosing between LocalDockerDelivery (named-volume,
assumes a filesystem-shared local daemon) and RemoteDockerDelivery
(docker cp, for a daemon on a different host), but setup.go's
newAgentDelivery() never set it. Routing fell through to
dockerDelivery's own isDockerLocal(), which read the ambient
os.Getenv("DOCKER_HOST") instead of the workspace's resolved
Agent.Docker.Env["DOCKER_HOST"] actually used for docker subprocess
calls -- explaining logs showing "using local docker delivery (named
volume)" despite DOCKER_HOST=ssh://... being configured.
- setup.go: set IsRemoteDocker via docker.RemoteDockerHost(dockerEnv).
- factory.go: removed the redundant/buggy isDockerLocal check; routing
is decided once, at the call site, from the real env.
- setup_test.go: added TestNewAgentDelivery_RemoteDockerHostWiring
covering unix/unset (local) and ssh/tcp (remote) DOCKER_HOST cases.
Documentation (docker-provider-via-wsl.mdx):
- Step 1: lead with wsl --install, then the full manual dism path
enabling both Microsoft-Windows-Subsystem-Linux and
VirtualMachinePlatform, followed by wsl --set-default-version 2.
- Step 2: note Steps 3 and 5 require systemd enabled in WSL.
- Step 8: document the mount-path translation behavior and that
\\wsl.localhost\... UNC paths are not supported for mounts.
- New Step 9: connecting the editor over Devsy's built-in SSH tunnel,
with a troubleshooting note on DEVSY_DEBUG=true for the editor
write EPIPE error.
Verification: go build ./... OK; go test ./pkg/driver/docker/...
./pkg/agent/delivery/... ./pkg/devcontainer/... all pass; go vet and
gofmt clean.
Related issue: #977
Summary
Fixes the WSL2 setup instructions in the
Docker provider via WSLtutorial and two underlying bugs that broke workspaces using anssh://Docker daemon on Windows. All three were surfaced via #977.Technical fixes
1. Mount-path translation (
EnsurePath)Problem:
pkg/driver/docker/runargs.goEnsurePathonly converted Windows drive paths to their WSL/mnt/<drive>form whenDOCKER_HOSTwastcp://. The documented WSL setup (#977, thedocker-provider-via-wsltutorial) uses anssh://DOCKER_HOSTpointing at a Docker daemon inside the WSL2 VM, so workspace mounts were passed through as raw Windows paths that the WSL daemon could not resolve. This caused the workspace build/tar failures the user hit before reaching the editor SSH stage.Change:
EnsurePathto any non-localDOCKER_HOST(i.e. notunix://ornpipe://), covering bothtcp://andssh://endpoints, via the newdocker.RemoteDockerHosthelper (pkg/docker/helper.go) — matching the semantics already used bypkg/agent/delivery/factory.go.windowsToWSLPathhelper.TestWSLPathConversion,TestIsRemoteDockerHost,TestIsRemoteDockerHostEnv, plus a non-Windows no-op guard in the driver suite.2. Agent delivery routing never checked the real
DOCKER_HOSTProblem:
pkg/agent/delivery/factory.go'sNewAgentDeliveryalready had anIsRemoteDockerfield that takes priority for choosing betweenLocalDockerDelivery(named-volume, assumes a filesystem-shared local daemon) andRemoteDockerDelivery(docker cp, for a daemon on a different host).pkg/devcontainer/setup.go'snewAgentDelivery()never set it, so routing always fell through todockerDelivery's ownisDockerLocal()check — which read the ambientos.Getenv("DOCKER_HOST")instead of the workspace's resolvedAgent.Docker.Env["DOCKER_HOST"](the value actually passed to the docker subprocess calls). This is why the reporter's logs showusing local docker delivery (named volume)despiteDOCKER_HOST=ssh://avencod@localhostbeing configured and used.In practice this rarely surfaced as a hard failure —
LocalDockerDelivery.populateVolumetries a helper-container copy first, which still works over anssh://transport — but it silently defeated theRemoteDockerDeliverypath this repo already built for non-local daemons, and would misbehave if that helper-copy fallback ever fails (it falls back to writing directly to the volume's host-filesystem mountpoint, which is unreachable from Windows for a WSL-side daemon).Change:
setup.go: setIsRemoteDocker: docker.RemoteDockerHost(dockerEnv), reusing the helper added for fix Rebrand devpod/loft to devsy #1.factory.go: removed the now-redundant/buggyisDockerLocalos.Getenvcheck —dockerDeliveryis only reached once the caller has already classified the daemon as local, so there is a single source of truth for the local/remote decision.setup_test.go: addedTestNewAgentDelivery_RemoteDockerHostWiring(unix/unset → local, ssh/tcp → remote). Verified it fails against the pre-fix code and passes after.Documentation
wsl --install(recent Windows builds), then the full manualdismpath enabling bothMicrosoft-Windows-Subsystem-LinuxandVirtualMachinePlatform, followed bywsl --set-default-version 2(the original step only enabled WSL, not WSL2).systemctl, which requires systemd enabled in WSL./mnt/<drive>), and that\wsl.localhost\...UNC paths are not supported for mounts.write EPIPEerror viaDEVSY_DEBUG=true.Verification
go build ./...OKgo test ./pkg/driver/docker/... ./pkg/agent/delivery/... ./pkg/devcontainer/...— all existing + new tests passgo vetclean,gofmtcleanNotes / follow-ups
Still out of scope here (needs Windows/WSL reproduction, not something to guess at blind): the reporter's
write EPIPEfrom VSCodium's ProxyCommand during the IDE SSH stage. Traced the call path (workspace ssh --stdio→ContainerTunnel.Run→runHostTunnel/runInContainer→ nesteddocker exec/sshsubprocess spawns, none with a console, all under a process VSCodium itself spawned with piped stdio).agent.local: truein the docker provider template is not the bug — tracedagent.InjectAgent'sIsLocalbranch and confirmed the local path runs the outer SSH-server in-process via an emulated shell (no extra subprocess), so flipping it to "remote" for anssh://daemon would regress the working case, not fix it. The nesting itself (docker CLI → ssh.exe, spawned from an SSH-session-spawned subprocess, spawned from a ProxyCommand-spawned subprocess, all pipe-only stdio) is the more likely culprit, but pinning down which hop actually fails needs either:DEVSY_DEBUG=truestructured tunnel logs from a reporter still hitting this (requested in Step 9's troubleshooting note), orDOCKER_HOST=ssh://(not just Podman's own machine/named-pipe transport) on thewindows-latestrunner already used by theup-docker-wsllabel — this needs provisioning a real WSL distro withsshd+ a docker daemon, which isn't in CI today and is worth tracking as its own PR rather than bundling into this one.ssh://docker host path (see above) would help confirm/deny the hop.Related issue: #977