Tunneling from Docker on a Mac
originally published at https://blog.devgenius.io/tunneling-from-docker-on-a-mac-5c5395d791f7?source=rss-b06e64bd1a8d------2
This is probably more suited to be a Stack Overflow answer, but I figured the answer is so interesting, I’d write a Medium post about it.
First of all, let’s define the problem:
Until this day, developers that wanted to create an SSH tunnel for development purposes only had to spin up another terminal and run an SSH command with a forward tunnel command, e.g.
ssh -N -L 3306:mysqlserver.internal:3306 user@bastion.com
They would then configure their development environment to connect to localhost:3306 for their MySQL server, instead of connecting directly to the unexposed mysqlserver.internal.
Tunneling without Docker
In 2020, you are likely using Docker to develop your very cool application, which means your application’s network is no longer your host’s network. This means that naively connecting to localhost:3306 will simply not work.
Tunneling with Docker
Uh oh… so what do we do then?
So now that we understand the problem, let’s talk about how to solve it.
We can take a few approaches: run your docker on the host network, find a way to connect back to the host from within the docker, or run the tunnel inside a docker (either your application docker or a separate docker within the same docker network).
Running your Docker on the host network
Running your docker on the host network can be done with the following command:
docker run —network host
This is the most ideal solution because it simply cancels out the Docker network. But what’s the problem?
The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.
(Read more here: https://docs.docker.com/network/network-tutorial-host/)
So if you’re a Linux developer, you can probably hit Ctrl+W and go read something else. But those who use Macs and Windows machines can’t do this using the Docker Desktop client as of today.
Connecting back to your host
Apparently, it is possible to connect back to your host from within your docker. The problem is that you must look at the existing interfaces and find the IP address of your host, instead of using a fixed hostname, like “localhost”.
You can read more about how to do this in this Stack Overflow answer.
So, this solution can easily work for you if you want something quick and dirty. But if you want to set up a proper development environment, you can’t rely on finding your host’s IP address every time.
Running the tunnel inside your container
Another straightforward solution, but should be avoided due to Docker’s best practices. So let’s just skip this option.
Running the tunnel inside a dedicated container on a new Docker network
Probably the cleanest and most robust method to do this is to dockerize your tunnel, and connect to your MySQL server as if it was a remote host.
This can be done either manually, or using a Docker Compose. We’ll do it manually so we can explain what’s going on better. This requires us to do 3 things:
- Create a new Docker network
- Start the docker you want to have access in the network you created
- Build a tunnel docker and have it on the network you created
How does it look like?
Tunneling with a dedicated container
To create the docker network, we simply need to run:
docker network create mynetwork
Now that we have a new network, we can use the —network mynetwork parameter on our application and tunnel dockers.
Instead of building a Dockerfile, let’s use a one-liner to do the SSH, since we can easily find Dockers on Docker Hub which already have SSH installed and somewhat configured.
Here it is:
docker run \
—network mynetwork \
—name dbtunnel \
-v ~/.ssh:/root/.ssh \
kroniak/ssh-client \
ssh -v -o StrictHostKeyChecking=no \
bastion.com \
-N \
-L 0.0.0.0:3306:mysql.internal:3306
In this example, I used kroniak/ssh-client which seemed to be working nicely, but I’m pretty sure there are many others that already have SSH preinstalled.
Before we use it as a one-liner, we need to set up the container with three important parameters:
- The network parameter, which we previously mentioned, so that there will be connectivity between your application and the tunnel docker
- The name parameter, which sets the hostname of the docker. In this example, dbtunnel is the chosen name, and this will be the hostname your application will connect to for it to be able to reach the database.
- A mechanism to pass your SSH key in — either mapping the .ssh a directory into your docker (easy) or using ssh-agent which requires a bit extra work, but overall is a better approach.
Now that we have the docker command set up, we can finally construct the SSH command. A few important pointers:
- The -o StrictHostKeyChecking=no parameter needs to be provided so that the connection will fail due to the inability of the container to add the hostname you are connecting to, to the list of known hosts. This is because the command has no disk state it can store this, nor is an interactive application. We can make it store state, but it is bad practice if state is not needed with containers.
- The tunneling command -L needs to have a fourth section which tells the container to listen for connections coming in from anywhere (hence the 0.0.0.0) instead of the default, which is localhost 127.0.0.1. Keep in mind that localhost here refers to the dbtunnel docker and not your host.
- The -N and -v commands just make the command more usable — they remove the interface and make it more verbose.
Happy tunneling!
Tunneling from Docker on a Mac was originally published in Dev Genius on Medium, where people are continuing the conversation by highlighting and responding to this story.