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

  1. Install an ssh client: `apt install openssh-client` / `apk add openssh-client` / enable Windows OpenSSH Client feature.
  2. Verify `which ssh` in the exact environment lazydocker is launched from.
  3. If ssh exists but PATH is wrong in GUI launches, launch lazydocker from a shell or fix the launcher's environment.
  4. 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

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


AI-assisted analysis of jesseduffield/lazydocker@7e7aadc207 (2026-08-15). Data as JSON: /api/errors/9c8d2ae738322bf6. Report an issue: GitHub.