jesseduffield/lazydocker · error
tunnel docker host over ssh: %w
Error message
tunnel docker host over ssh: %w
What it means
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.
Source
Thrown at pkg/commands/ssh/ssh.go:97
oSCommand CmdKiller
}
var _ io.Closer = (*tunneledDockerHost)(nil)
func (t *tunneledDockerHost) Close() error {
return t.oSCommand.Kill(t.cmd)
}
func (self *SSHHandler) createDockerHostTunnel(ctx context.Context, remoteHost string) (*tunneledDockerHost, error) {
socketDir, err := self.tempDir("/tmp", "lazydocker-sshtunnel-")
if err != nil {
return nil, fmt.Errorf("create ssh tunnel tmp file: %w", err)
}
localSocket := path.Join(socketDir, "dockerhost.sock")
cmd, err := self.tunnelSSH(ctx, remoteHost, localSocket)
if err != nil {
return nil, fmt.Errorf("tunnel docker host over ssh: %w", err)
}
// set a reasonable timeout, then wait for the socket to dial successfully
// before attempting to create a new docker client
const socketTunnelTimeout = 8 * time.Second
ctx, cancel := context.WithTimeout(ctx, socketTunnelTimeout)
defer cancel()
err = self.retrySocketDial(ctx, localSocket)
if err != nil {
return nil, fmt.Errorf("ssh tunneled socket never became available: %w", err)
}
// construct the new DOCKER_HOST url with the proper scheme
newDockerHostURL := url.URL{Scheme: "unix", Path: localSocket}
return &tunneledDockerHost{
socketPath: newDockerHostURL.String(),
cmd: cmd,View on GitHub (pinned to 7e7aadc207)
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.
Example fix
# before (alpine container, no ssh) / # lazydocker # tunnel docker host over ssh: exec: "ssh": not found # after / # apk add openssh-client && lazydocker
Defensive patterns
Strategy: validation
Validate before calling
if _, err := exec.LookPath("ssh"); err != nil {
return fmt.Errorf("ssh client required for DOCKER_HOST=ssh://: install openssh-client")
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- {stderr}
- tunnel ssh docker host: %w
- override DOCKER_HOST to tunneled socket: %w
- create ssh tunnel tmp file: %w
- ssh tunneled socket never became available: %w
AI-assisted analysis of jesseduffield/lazydocker@7e7aadc207 (2026-08-15).
Data as JSON: /api/errors/9c8d2ae738322bf6.
Report an issue: GitHub.