I’m a big fan of the new Windows Subsystem for Linux (WSL). It adds that missing piece for doing development on Windows: a bash prompt running on Linux. Now that may seem crazy, but most folks I know that used to run Windows on their primary development machine moved over to a MacBookPro primarily for the terminal and the ability to work in a Linux-like environment.
I’m also a huge fan of Docker. Originally Docker was only something that worked on Linux, but creative folks out there came up with a seamless way to install a VM with Docker and get the cli tool working just like you would expect on Linux. So now its easy to use Docker for Mac or Docker for Windows and leverage all the power that comes with Docker.
Docker on the WSL
There are some things to be aware of with both of these crazy cool new tools. While the WSL is running Ubuntu, it’s not a VM, so there are limitations on what you can and cannot do. The one I’ll cover is running Docker inside the WSL. Long story short, it turns out you can’t. You can install it without errors, but when you try to use the Docker cli you’re greeted with an error message:
Cannot connect to the Docker daemon at tcp://localhost:2375. Is the docker daemon running?
The good news is that the error message is 100% correct. We can’t connect to the docker daemon because it’s not (and won’t) started. Not a huge deal, we have Docker for Windows, so let’s just use Docker from the Windows side.
Docker for Windows Permissions
So Docker for Windows is great with one major exception. If you want to build a Docker image that is based on Linux, you’re going to have permission issues. There is no mapping between NTFS permissions and Linux file permissions because Docker is actually creating a tarball of the files behind the scenes, and sends that to the Docker daemon. That said,
docker build still does work, but you see the following message when building an image:
SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.
Ok, so, not great. We could add a final script at the end of our Dockerfile to reset the permissions for our files, but that’s a pain and not part of a standard Docker workflow.
Docker cli / daemon communications
So the next step I went with was to build my images from inside the WSL, so the permissions are correct and I get to use standard Dockerfiles without any craziness. The question was how since Docker doesn’t run in the WSL and Docker for Windows is actually running inside a VM in Hyper-V.
An important concept to understand is how the Docker cli normally communicates with the Docker daemon. By default on Linux, the Docker daemon only listens on a Unix socket at
/var/run/docker.sock. Unix sockets don’t exist on Windows, so how is the Docker cli tool communicating on Windows when using Docker for Windows? The answer is by using the Windows equivalent to Unix sockets, Named Pipes.
You’ll notice we run into a chicken and egg problem. Our Docker daemon is running in a Linux VM which doesn’t support Named Pipes, and our client is running in Windows which doesn’t support Unix sockets. The answer is a service that gets installed called the Docker for Windows Service. It starts a proxy which is the bridge between the Windows Named Pipe and the Docker daemon running in the Hyper-V VM. What I don’t know is how they are actually communicating with the Docker daemon inside the VM. It appears that they are using the Hyper-V Sockets API to proxy to the Unix socket. Unfortunately, Docker for Windows is not open source, just free, so we can only guess.
At one time, TCP/2375 was accessible so all you had to do was set your DOCKER_HOST environment variable inside the WSL to use TCP to connect to the daemon, and the Docker cli would connect back to the VM, and success, Docker from inside the WSL. There are multiple issues across GitHub that say it worked at one time; even I enjoyed this workflow for a little bit. Docker for Windows has recently disabled that setting. The easiest option would be to simply turn that back on. The problem is that your Docker daemon is now accessible from other machines, which is a security concern. The Docker for Windows UI even tells you as much.
You could probably protect yourself with a Windows Firewall rule to prevent any outside connections into 2375, but I haven’t tested that yet.
And the missing option is to enable TCP/2375 with TLS. From my research, it’s not possible to enable TLS in Docker for Windows today, but it is on the road map. In the long run, that would be the way to go.
Another option is to create our own proxy that utilizes the Hyper-V Sockets API to connect into the same proxy that the Docker Windows service uses. Based on information in the Registry and the Docker for Windows log files, I think we have all the information we need to create the connection. I just need to dust off my C++ skills, or maybe even try this out in Rust. (We need to use raw sockets to create the Hyper-V Socket connection, so I can’t use my default of Node.js)
I’d love to find an easier, built-in solution, but it looks bleak for now, unless I take the time to code up my own proxy from Solution 2. Stay tuned to see what develops.