{"record":{"id":"9c8d2ae738322bf6","repo":"jesseduffield/lazydocker","slug":"tunnel-docker-host-over-ssh-w","errorCode":null,"errorMessage":"tunnel docker host over ssh: %w","messagePattern":"tunnel docker host over ssh: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/commands/ssh/ssh.go","lineNumber":97,"sourceCode":"\toSCommand  CmdKiller\n}\n\nvar _ io.Closer = (*tunneledDockerHost)(nil)\n\nfunc (t *tunneledDockerHost) Close() error {\n\treturn t.oSCommand.Kill(t.cmd)\n}\n\nfunc (self *SSHHandler) createDockerHostTunnel(ctx context.Context, remoteHost string) (*tunneledDockerHost, error) {\n\tsocketDir, err := self.tempDir(\"/tmp\", \"lazydocker-sshtunnel-\")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create ssh tunnel tmp file: %w\", err)\n\t}\n\tlocalSocket := path.Join(socketDir, \"dockerhost.sock\")\n\n\tcmd, err := self.tunnelSSH(ctx, remoteHost, localSocket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"tunnel docker host over ssh: %w\", err)\n\t}\n\n\t// set a reasonable timeout, then wait for the socket to dial successfully\n\t// before attempting to create a new docker client\n\tconst socketTunnelTimeout = 8 * time.Second\n\tctx, cancel := context.WithTimeout(ctx, socketTunnelTimeout)\n\tdefer cancel()\n\n\terr = self.retrySocketDial(ctx, localSocket)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ssh tunneled socket never became available: %w\", err)\n\t}\n\n\t// construct the new DOCKER_HOST url with the proper scheme\n\tnewDockerHostURL := url.URL{Scheme: \"unix\", Path: localSocket}\n\treturn &tunneledDockerHost{\n\t\tsocketPath: newDockerHostURL.String(),\n\t\tcmd:        cmd,","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/jesseduffield/lazydocker/blob/7e7aadc2071d58031bf2daafca1fbd4093efc23f/pkg/commands/ssh/ssh.go#L79-L115","documentation":"createDockerHostTunnel calls tunnelSSH, which runs `ssh -L <localSocket>:/var/run/docker.sock <host> -N` via exec.CommandContext and startCmd. This error wraps failure to *start* the ssh process — not ssh's own protocol errors. The %w chain carries the exec error, most commonly exec: \"ssh\": executable file not found in $PATH.","triggerScenarios":"DOCKER_HOST=ssh://user@host while no ssh client binary is on PATH (minimal containers, Windows without OpenSSH, stripped images), PATH is broken in the launching environment, or the OS refuses to fork the process (resource limits).","commonSituations":"lazydocker run inside a slim/alpine container without openssh-client installed; Windows where ssh.exe is not on PATH; GUI launchers with a sanitized PATH; devcontainer images missing ssh.","solutions":["Install an ssh client: `apt install openssh-client` / `apk add openssh-client` / enable Windows OpenSSH Client feature.","Verify `which ssh` in the exact environment lazydocker is launched from.","If ssh exists but PATH is wrong in GUI launches, launch lazydocker from a shell or fix the launcher's environment.","Alternatively pre-tunnel manually from a machine that has ssh and point DOCKER_HOST at the forwarded unix socket."],"exampleFix":"# before (alpine container, no ssh)\n/ # lazydocker   # tunnel docker host over ssh: exec: \"ssh\": not found\n\n# after\n/ # apk add openssh-client && lazydocker","handlingStrategy":"validation","validationCode":"if _, err := exec.LookPath(\"ssh\"); err != nil {\n    return fmt.Errorf(\"ssh client required for DOCKER_HOST=ssh://: install openssh-client\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Install openssh-client in any container/image that runs lazydocker with an ssh DOCKER_HOST.","Verify `which ssh` in the launching environment (GUI launchers often strip PATH).","Prefer launching lazydocker from a login shell so PATH includes standard tool directories."],"tags":["docker","ssh","exec","path","missing-binary","setup"],"backgroundTag":null,"analyzedSha":"7e7aadc2071d58031bf2daafca1fbd4093efc23f","analyzedAt":"2026-08-15T09:51:48.093Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}