Using Docker From the Windows Subsystem for Linux, Part 2
In the first post, Using Docker From the Windows Subsystem for Linux, I walked through the challenges of trying to use the Docker form inside the WSL to access the Docker for Windows installation. After playing around a bit top understand how Docker for Windows works, I finally have my machine setup for this workflow.
Docker for Windows utilizes Hyper-V to run a custom Linux VM where the Docker engine runs. This makes it very easy for us to get the Docker expierience we're used on to Linux. The primary supported configuration is running the Docker cli from within Windows, as you'd expect. The WSL wasn't really on their radar, so we have some challenges when trying to interoperate across Windows, Docker for Windows, and the WSL.
Accessing the Docker for Windows VM
Under Linux and Mac, Docker uses a unix socket for communication to the local Dcoker engine. You can optionally enable a TCP endpoint for remote access. Windows doesn't support unix sockets, but it does have it's own equivalent: Named Pipes.
On Windows, the Docker cli defaults to connecting to the Docker engine on the named pipe of
//./pipe/docker_engine. The traffic is the same HTTP requests that Docker normally sends. Playing around, I had no problems connecting to it and sending Docker commands in a C# app.
The interesting part was how Docker for Windows communicates into the VM. On the Windows side, there is a service running that proxies the named pipe traffic into the VM using Hyper-V Sockets. There is very little posted about them, but I did find this site which really helped explain what they are. Basically, Hyper-V sockets are in-memory connections which can be used to communicate into Hyper-V containers without having to hit the network stack. They are connected to very similarly to the other IP protocols (TCP/UDP) but they have a custom network family and address format (GUIDs). By being a layer on top of IP, they can be implimented cross-platform using traditional C socket programming.
The final option, which I mentioned last time is to enable the TCP endpoint. Docker for Windows give your a nice warning that enabling this setting is insecure and could be dangerous. And they are right, but there is a very simple way to protect ourselves we'll talk about in a bit.
Installing Docker cli in the WSL
A common question I saw folks asking was if we could just run Docker natively inside the WSL. Unfortunately, that isn't possible today. I'm not familiar with all of the details that prevent it, but the daemon doesn't start, and the installation doesn't cleanly complete. There is also no cli-only package we can install with
apt-get. So we're stuck with a more manual installation.
There is a site that maintains the Docker builds that has a helpful script for maunally downloading the Dcoker binaries. https://get.docker.com/builds/ contains a one liner for downloading the latest version. It's supposed to be run as
root, so make sure to add
sudo before each command if you want to run the command as another user.
It turns out that there's one additional step. Using the offical deb package, you also get the dependency library
libltdl7. The Docker cli give you a message indicating that the library's binary is missing, but not much else. Simply
sudo apt-get install libltdl7 and you're all set.
Putting Everything Together
After playing around with all the various options I landed on just doing the simpliest solution. I had grand ideas of a Windows service that would proxy the traffic from the WSL directly into Hyper-V, because I'm a developer, so I reach for a coding solution first. The reality is that's also the most complicated. There are just three steps I had to do.
Enable the TCP endpoint in the Docker for Windows settings. Yes, it's insecure, but let's handle that next.
Using the Windows Firewall, block external connections to TCP/2375. To start, open a PowerShell Console, as an Administrator. The command is:
New-NetFirewallRule -DisplayName "Docker for Windows TCP" -Action Block -Direction Inbound -EdgeTraversalPolicy Block -Enabled True -LocalPort 2375 -Protocol TCP
That blocks all external, inbound traffic to TCP/2375.
- Finally, configure the Docker cli inside the WSL to communicate on
tcp://localhost:2375. The easiest way it to set the
DOCKER_HOSTenvironment variable. You can make it permenant by adding it to your shell's RC file. By default, that's
You will need to restart your shell to have it take effect.
To test it out, I like to run
docker ps -a to see a list of all the containers that are running.
And that's it!
After all that, I can now do a Docker workflow that makes sense to me. I can use Visual Studio Code to write my apps, then on the WSL side, build/rebuild the Docker image with correct permissions, and either test it locally or push it up to Docker Hub for deployment.
I'd love to hear how you're using Docker in the WSL and what your workflow looks like as well. Always room for improvement!