Skip to content

Instantly share code, notes, and snippets.

@jlfwong
Last active April 26, 2025 00:04
Show Gist options
  • Save jlfwong/9114ee24bcac91db6c7b086a5d705849 to your computer and use it in GitHub Desktop.
Save jlfwong/9114ee24bcac91db6c7b086a5d705849 to your computer and use it in GitHub Desktop.

SSH forwarding for Docker Desktop for Mac which respects $SSH_AUTH_SOCK

This is a quick tutorial on how to get SSH key forwarding working on Docker Desktop for Mac in situations where you want control of which ssh-agent server is being used, rather than the default.

You might want to do this to e.g. forward a specific SSH key into a docker container.

Docker has support for general SSH key forwarding, but this doesn't respect $SSH_AUTH_SOCK.

Background on ssh-agent

When you use ssh, or git over the SSH protocol (e.g. when cloning a repo with a [email protected] URL), Unix systems will use ssh-agent to find relevant keys. From the man page of ssh-agent:

     ssh-agent is a program to hold private keys used for public key
     authentication.  Through use of environment variables the agent can be
     located and automatically used for authentication when logging in to other
     machines using ssh(1).

Under the hood, this uses two key environment variables to control its behavior:

ENVIRONMENT
     SSH_AGENT_PID  When ssh-agent starts, it stores the name of the agent's
                    process ID (PID) in this variable.

     SSH_AUTH_SOCK  When ssh-agent starts, it creates a UNIX-domain socket and
                    stores its pathname in this variable.  It is accessible
                    only to the current user, but is easily abused by root or
                    another instance of the same user.

When you start a new SSH server with ssh-agent -s, it will echo the appropriate variables to see the newly started process ID, and the socket path to communicate with this new server.

Background on Docker's implementation of SSH forwarding

Docker's implementation of SSH key forwarding works in two parts via two flags to the docker run command:

The first mounts a file from the host system into the container file system. The bizarre part here is that this file does not exist on the host system. /run/host-serives/ssh-auth.sock is a magical file that Docker Desktop for Mac is presumably synthesizing on demand.

--mount type=bind,src=/run/host-services/ssh-auth.sock,target=/run/host-services/ssh-auth.sock

The second is a command to set the SSH_AUTH_SOCK environment variable inside the DOCKER container:

 -e SSH_AUTH_SOCK="/run/host-services/ssh-auth.sock"

The combination of these does correctly forward ssh-agent inside the container to the ssh-agent in the host system, but unfortunately gives you no control over which ssh-agent process on the host machine it connects to. Presumably, it's whichever one was set in the environment when docker was launched.

In a fresh shell on my Mac, I get the following:

echo $SSH_AUTH_SOCK
/private/tmp/com.apple.launchd.kqwbiruqUO/Listeners

I could presumably control this by rebooting the docker runtime every time I wanted to change which keys I want to expose to my docker container, but this is pretty annoying, and also doesn't allow me to expose different SSH keys to different docker containers. Let's do better.

Trying to mount the host system $SSH_AUTH_SOCK file inside the container

Intuitively, the most straightforward way of doing this would be to just mount the socket file from the host system into the docker container. Like this:

$ eval $(ssh-agent -s)
Agent pid 3339

$ echo $SSH_AUTH_SOCK
/var/folders/80/29_gz6bj01nb95w7vf2ty_rw0000gn/T//ssh-dR35S5FWDqJ8/agent.3338

$ docker run --mount type=bind,src=$SSH_AUTH_SOCK,target=/run/host-services/ssh-auth.sock whatever-image-name ssh-add -l
docker: Error response from daemon: invalid mount config for type "bind": stat /host_mnt/private/var/folders/80/29_gz6bj01nb95w7vf2ty_rw0000gn/T/ssh-dR35S5FWDqJ8/agent.3338: operation not supported

Unfortunately, as you can see, this doesn't work. Docker will refuse to mount an macOS socket file as a unit socket file. So what can we do?

Using socat to forward ssh-agent in the container to an arbitrary ssh-agent in the host

We can't mount sockets, but we can create a socket inside the container, forward its connection over TCP to the host system, and then run a TCP server in the host system to connect it to whatever socket we want to.

We could write servers ourselves to do this, but there's already a handy utility called socat that does just what we need.

Starting on the host system:

$ brew install socat
$ eval $(ssh-agent -s)
Agent pid 3339
$ echo $SSH_AUTH_SOCK
/var/folders/80/29_gz6bj01nb95w7vf2ty_rw0000gn/T//ssh-dR35S5FWDqJ8/agent.3338
$ ssh-add 
$ nohup socat \
      TCP-LISTEN:2222,bind=127.0.0.1,reuseaddr,fork \
      UNIX-CONNECT:$SSH_AUTH_SOCK > /tmp/socat.log 2>&1 &
[1] 10894

Now we have a TCP server running on 2222, accessible only on localhost, which will act as a pipe to the macOS socket referenced by $SSH_AUTH_SOCK (/var/folders/80/29_gz6bj01nb95w7vf2ty_rw0000gn/T//ssh-dR35S5FWDqJ8/agent.3338 in the case of this example).

Next, we need to set up the socket forwarding on the other side. Let's start a docker container:

$ docker run -it -e SSH_AUTH_SOCK="/tmp/ssh-agent.sock" whatever-image-name bash
container-user@7408b8ad773:/workspace$ apt-get install -y socat

container-user@7408b8ad773:/workspace$ nohup socat \
    UNIX-LISTEN:/tmp/ssh-agent.sock,fork,mode=666 \
    TCP:host.docker.internal:2222 > /tmp/socat.log 2>&1 &

container-user@7408b8ad773:/workspace$ file /tmp/ssh-agent.sock
/tmp/ssh-agent.sock: socket
container-user@7408b8ad773:/workspace$ ssh [email protected]
Hi jlfwong-bot! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment