Replacing Docker Desktop on Windows

24/12/2021

In January 2022 it will be time to pay up, if you want to continue using Docker Desktop on Windows (and Mac) unless you are a small company or individual user. Whether the price is worth it I will let you decide, but personally I’m not going to pay so I need to find a replacement. In this blog post I will describe the solution that I’m going to use going forward.

The idea

I really like WSL (Windows Subsystem for Linux), it works great and after Docker Desktop got support for WSL2 the docker experience on Windows got a lot more stable. This had me wondering what is it that Docker Desktop actually provides of value (except at weekly prompt that it needs to update), and half the times it fails at at that.

My docker usage is not super advanced, I build some dotnet images and I run different containers for local testing before I usually just deploy it to the cloud. So all I really need, is the ability to run docker build, run, ps and logs. With WSL2 I know that port mapping works and mounting also works fine if I just remember to user /mnt/c/what-ever. So in reality for me the added value of Docker Desktop, is that it gives me access to the docker cli from all my windows terminals and makes sure I dont have to think about whether WSL is started, and I can map windows paths. So how can I get the same without buying Docker Desktop?

One approach is just to ditch the Windows terminal and start doing everything in WSL, however being a windows user I will probably have a hard time to get used to changing my workflows. So that idea is not the one I will pursuit today.

An alternative is to find a way to get the docker-cli installed on Windows and have it communicate with the docker daemon running inside WSL2. This is essentially what Docker Desktop does, with some extra magic. So my goal is to find a way to do this, in essence I need to do the following:

Install Docker in WSL2

How to install docker in WSL depends on which Linux distribution you are running. I’m using Ubuntu 18, so for me the following steps was necessary

sudo apt update

sudo apt install docker.io -y

To ensure docker is started automatically wih WSL a few extra steps is required. I followed the guide from Sung Kim

First step is to allow the docker daemon (dockerd) to run without sudo. Run

sudo visudo

and add the following line to the bottom, replacing <your-username> with your WSL username

# Docker daemon specification
<your-username> ALL=(ALL) NOPASSWD: /usr/bin/dockerd

In addition we need to start dockerd, I went with Sung Kims approach to add it to the .bashrc file

nano ~/.bashrc

# Start Docker daemon automatically when logging in if not running.
RUNNING=`ps aux | grep dockerd | grep -v grep`
if [ -z "$RUNNING" ]; then
    sudo dockerd > /dev/null 2>&1 &
    disown
fi

Finally it is nice to be part of the docker user group, so that docker commands can be run without sudo.

sudo usermod -aG docker $USER

Fix communication between Windows and WSL2

The best way to do this would be if I could connect to WSL2 using ssh. Because with ssh I can tunnel the docker socket from WSL2 to Windows which would provide an easy way to access the docker daemon. Luckily I’m not the first one to get the idea, that it would be great to be able to ssh into WSL2, Scott Hanselman have a great article that describes exactly how to do that.

To enable ssh, we have to do the following from within WSL.

  1. Enable ssh and change the port
  2. Start ssh
  3. Add our public key

On the Windows side, we have to

  1. Map the port used
  2. Enable access through the windows firewall

Now you should be able to SSH into WSL from a windows terminal. If that works you can also create an SSH tunnel that forwards the docker.socket from WSL to a tcp port on Windows of your choice.

The command I use to make this magic happen is

ssh -nNT -L 0.0.0.0:2300:/var/run/docker.sock [email protected] -p 2222

What this command does is that it forwards all traffic from the local port 2300 to the docker.sock socket inside WSL. Obviously you have to replace sjkp with your WSL username and the IP 172.31.133.163 with IP your WSL is using and port 2222 with the port you picked when you setup ssh in WSL.

If you are using ubuntu like me and want the ssh-agent to start together with WSL, you can run

sudo visudo

and add

<your-username> ALL=(ALL) NOPASSWD: /etc/init.d/ssh

Replacing <your-username> with your actual username, and then add the following to your .bashrc

# Start ssh daemon automatically when logging in if not running.
RUNNING=`ps aux | grep sshd | grep -v grep`
if [ -z "$RUNNING" ]; then
    sudo /etc/init.d/ssh start
fi

Install Docker-CLI on Windows

Now how do we install docker-cli on Windows, without relying on Docker Desktop? Luckily for us, we don’t have to do much here, as the docker-cli is open source and there are always some nice folks that creates binaries of the various open source libraries that we can just take and use. Stefan Scherer is one of those guys on his github you can find recent builds of the docker-cli built for Windows. And since docker-cli is programmed in Go which makes making self-contained executables a breeze all we have to do is to download docker.exe and we have the cli for Windows.

If you need docker-compose, you can grab that directly from the docker folks at https://github.com/docker/compose/releases

Ensure docker-cli sends commands to docker running inside WSL2

Now the last piece of the puzzle is to configure docker cli (or docker-compose) to use the forwarded docker.socket we created earlier. Luckily that part is also super easy.

If you set the environment variable DOCKER_HOST to

DOCKER_HOST=tcp://0.0.0.0:2300

then the docker cli will automatically send all commands to that host, which as a result of our tunnel will run the commands inside our WSL2 environment.

With that in place we now essentially have a Docker Desktop alternative, that cost nothing except a more involved setup process.

Obviously you have to run a few commands for it to work, but you can always automate that.

Known Limitations

For now the most obvious limitations is that your have to use fully qualified paths that aligns with your Linux environment. So /mnt/c/<whatever> and no relative paths.

I also have some problems with ctrl+c not always allowing me to get out of a running container, which is a bit annoying as I then have to close my terminal window.