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.
Table of Contents
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
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
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.
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.
- docker – Index of win/static/stable/x86_64/ (docker.com)
- docker compose – Releases · docker/compose (github.com)
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
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?
Undeniably everything works thanks to amazing feature of wsl.exe – which allows to seamlessly start processes inside of a WSL distribution.
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
socatthat connects to the target socket inside
- Proxy talks to
wsl.exeusing stdio, same as
wsl.exedoes 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)
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.