January 18, 2023

How to install and connect to Docker on WSL?

Have you ever tried replacing Docker Desktop by installing Docker on your WSL instance to discover that now you don’t even know how to connect to it from CLI or other development tools? Have you ever got following error message when trying to use Docker CLI?

error during connect: Get "http://%2F%2F.%2Fpipe%2FdockerDesktopLinuxEngine/v1.24/version": open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified.

Docker Desktop is really awesome at making things smooth for its users <3, but lets try to manage without it! We are going to install docker daemon and client tools manually and make them talk.

Before we start

Make sure you are running at least version 0.67.6 of WSL, which is the first one that comes with systemd support out of the box, required for everything we are about to do. If you are unsure simply run wsl -v

Importantly, run all the commands as an Administrator / root. You can become root with sudo -s command. It would be wise to turn off Docker Desktop for this tutorial, so we avoid any confusion.

Next, install .NET 7 Runtime on your Windows machine in case you don’t have it yet- verify using dotnet --list-runtimes

Not surprisingly most of you probably already have WSL installed, but in case you don’t you can quickly get it directly from Microsoft Store for Windows 10/11.

Installing Docker Daemon in WSL

First make sure that you have turned on systemd as described here. Then proceed with docker installation which is as simple as running single command. If systemd is not enabled correctly, you will notice immediately after running this command ๐Ÿ˜‰

sudo snap install docker

Next verify if docker has started by running docker version, which should return server information in result as shown below.


Properly installed docker showing Server version

Installing Docker CLI on Windows

First lets install CLI tools, unless you have Docker Desktop, then you will already have them installed. In other case see the most important ones listed below. After downloading extract them to a directory of your choosing and add the directory to PATH for ease of use.

Connecting to Docker in WSL

Finally we can focus on the main topic – connectivity. The most popular way of connecting to Docker in a development environment is to use Unix Sockets or Named Pipes. However the problem is that Windows does not talk to Unix sockets and vice versa. Still there are other possibilities, like using TCP instead, but this option comes with its own set of problems:

  • HTTP – simple and easy, but also not secure,
  • HTTPS – secure, but not simple to setup and maintain

Luckily it is possible to expose Unix Socket as a Named Pipeline making whole experience not only easy, but also secure. In fact it is as simple as running an additional docker container and installing one prerequisite ๐Ÿ™‚ Give it a try!

sudo apt install socat

sudo docker run -d \
  --name wslpipeproxy \
  --privileged \
  --pid=host \
  --restart=always \
  -v /mnt/c/:/app \
  devopsifyme/wslpipeproxy

# allow the container to start...

docker logs wslpipeproxy

After running above commands, find two commands in the logs and execute them in your Windows terminal. In fact, you now have full connectivity to our WSL deployed Docker! Easy as that!

docker context create dockerOnWSLUbuntu2204 --docker host=npipe:////./pipe/dockerOnWSLUbuntu2204
docker context use dockerOnWSLUbuntu2204

Now run docker version on Windows to verify the connectivity. Notice that OS/Arch property is now windows/amd64 for the Client. You can also see that Context property is set to the one we just created.

Find out more at devopsifyme/wslpipeproxy – Docker Image | Docker Hub

How does it work?

dotted lines represent logical connection
solid lines represent two way communication initiated by a client

Undeniably everything works thanks to amazing feature of wsl.exe – which allows to seamlessly start processes inside of a WSL distribution.

Pipeline Proxy

The proxy is implemented as a .NET 7 program that:

  • First proxy creates and listens to a named pipeline
  • Then for every new received connection it
    • starts a wsl.exe process with parameters that make it to…
    • … starts socat that connects to the target socket inside
  • Proxy talks to wsl.exe using stdio, same as wsl.exe does with socat

Once connection channel is established, only thing we need to do is to just forward bytes back and forth ๐Ÿ™‚ It is really that simple ๐Ÿ™‚ You can have a look at the code here devopsifyme-wslpipeproxy (github.com)

Docker Container

We also have an image devopsifyme/wslpipeproxy – Docker Image | Docker Hub, which greatly simplifies the deployment process an allows proxy to execute in context of a user (compared to system, if run as a system service).

How does it work? First of all it requires container to be privileged and have access to host PIDs. This allows it to execute commands (nsenter) on behalf of the host (WSL). This is important as first of all, we want to copy the proxy binaries to Windows file system, and then want to start it from within WSL a Windows process, our proxy. This in turn is possible because of another amazing feature of WSL – a custom handler for Windows executables registered in binfmt_misc.

Using container deployment model essentially lets Docker to control a Windows process via its host and WSL magic. You get a simplified deployment and management, with all the benefits you would expect from a container, like logging and automatic restarts. Therefore it is recommended over manual installation.

References

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.