Docker in Windows 11 using WSL2

ferarias
6 min readJan 5, 2022

--

I wanted to share my experience setting up an environment for development with containerized applications on Windows. The license for Docker Desktop is not expensive, but it’s not free anymore, so I wanted to check if there’s an easy and cheap alternative to Docker Desktop for Windows.

My research is based upon these two resources:

but my goal here is to go straight to set up a quick environment for working without too much hassle. No ceremony, not too much prose, just the real cake. In fact, if you already have a WSL Debian-based distribution you can skip to the “Windows side…” section and start reading from there.

I assume you have some basic knowledge of Docker and Linux. PowerShell and bash basic skills will be useful, too.

The game plan

The main goal here is to set up a containers runtime using WSL2, Ubuntu Linux and free packages. The secondary goal is being able to use it from both Linux and Windows.

To do so, we need to setup WSL2, Ubuntu, dockerd and containerd, then build docker-cli for Windows and finally wire them so they can talk to each other.

Pre-requisites

First, you need to set up your Windows so that you can use virtualized containers, as well as Windows Subsystem for Linux. Open a terminal and type:

Enable-WindowsOptionalFeature -Online -FeatureName 'Containers' -AllEnable-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Hyper-V' -AllEnable-WindowsOptionalFeature -Online -FeatureName 'VirtualMachinePlatform' -AllEnable-WindowsOptionalFeature -Online -FeatureName 'Microsoft-Windows-Subsystem-Linux' -All

Winget

To install software, I will be using winget, the package manager for Windows, since it’s the easiest way to install stuff on Windows these days (given there’s a package installer for it).

Windows Terminal

We will use console a lot, so I recommend using a decent terminal. Windows offers the excellent Windows Terminal, which can be obtained from Microsoft Store.

Note: Windows Terminal requires Windows 10 1903 (build 18362) or later

Powershell

Now we have the basics up and running, you should also install Microsoft PowerShell (not Windows PowerShell) to make your (console) life easier. Open a terminal session and type:

winget install --id 'Microsoft.Powershell' --scope machine

Ubuntu linux

You could also use any Debian-based installation, but I feel more comfortable in Ubuntu, so I’ll stick to this distribution.

PS> winget install Ubuntu

Once it is installed, just type:

PS> ubuntu

You will see a message like this:

Installing, this may take a few minutes

Once it finishes, you must create a default account. You can use whatever username you like; I usually use my first name, fernando, and an easy to remember password.

Please create a default UNIX user account. The username does not need to match your Windows username.

Ok, once you are done, you’ll see something like the following:

Installation successful!
Welcome to Ubuntu 20.04 LTS (GNU/Linux 5.10.60.1-microsoft-standard-WSL2 x86_64)
[...]

Now you have a Ubuntu Linux under WSL2 ready to play with. For now, just close the terminal with

exit

Meanwhile, on the Windows side…

Open a Windows Powershell (using Windows Terminal), and type the following to check everything is fine from the WSL perspective:

wsl --list

You should see the following:

Windows Subsystem for Linux Distributions:
Ubuntu (Default)

We will need also a folder to hold the docker-cli executable for Windows. If you already have one and it’s in your PATH, that’s fine. You’ll need it later. In case you don’t have one, let’s create one, for example: c:\tools, and add it to the PATH (for example, using this).

Install docker in WSL

Open a terminal window in WSL Ubuntu (again, you can use Windows Terminal)

Start by upgrading the packages:

sudo apt update && sudo apt upgrade

[…takes a while…]

Install pre-requisites

sudo apt install --no-install-recommends apt-transport-https ca-certificates curl gnupg2

Let’s make sure the certificate for Docker repo is trusted:

source /etc/os-release
curl -fsSL https://download.docker.com/linux/${ID}/gpg | sudo apt-key add -

Add the Docker repository to the list of available repos for packages:

echo "deb [arch=amd64] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" | sudo tee /etc/apt/sources.list.d/docker.list

Now update the packages and install Docker and Docker CLI

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

Add your user to the Docker group, named docker:

sudo usermod -aG docker $USER

Let’s create a configuration for the dockerd daemon:

sudo mkdir /etc/docker/
sudo vi /etc/docker/daemon.json

And add the following contents:

{
"hosts": [ "tcp://0.0.0.0:" ],
"tls": false
}

You can launch dockerd directly:

sudo dockerd

and stop with CTRL-C

or launch dockerd in the background:

sudo dockerd &

And stop it with:

sudo pkill dockerd

If you launch it in the foreground, just open a new Ubuntu terminal session to keep on firing commands.

Check the containerdprocess is listening:

ss -peanut

And expect something like:

Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 *:2375 *:* ino:41752 sk:2001 v6only:0 <->

Try everything’s fine in the Docker world with this command:

docker -H 127.0.0.1 run --rm hello-world

If you are successful, export DOCKER_HOST so you don’t have to type it every time:

export DOCKER_HOST="tcp://127.0.0.1:2375"

Extra. Avoid being asked for superuser password each time you run dockerd

As Jonathan Bowman suggests, you can avoid being asked for sudo password each time you launch dockerd.

sudo visudo

and add at the end of the file:

%docker ALL=(ALL) NOPASSWD: /usr/bin/dockerd

Build docker-cli for Windows

To build docker-cli for Windows we are going to use Docker, as suggested in their repo.

First, we need to clone the repo from Github:

git clone https://github.com/docker/cli.git
cd cli

Now, build Docker CLI for the Windows platform:

docker buildx bake
docker buildx bake --set binary.platform=windows/amd64

If everything goes well, you will find the docker-cli executable in the build folder. You just have to move it from there to your Windows local tools location (we set that up previously, remember?). In our case, C:\tools\

cp build/docker-windows-amd64.exe /mnt/c/tools

Back in the Windows zone

Let’s get back to Windows PowerShell. Move to your tools folder and rename the docker-windows-amd64.exe to simply docker.exe, so that we just have to type docker (remember your tools folder must be in your PATH)

C:\
cd c:\tools
ren docker-windows-amd64.exe docker.exe

We need to know the IP of the WSL host. To do so, we can use this command (taken from here):

wsl -- ip -o -4 -json addr list eth0 `
| ConvertFrom-Json `
| %{ $_.addr_info.local } `
| ?{ $_ }

With the obtained IP, try the following (replacing with your IP, of course):

docker -H <YOUR_WSL_IP> ps

Did it work? Congratulations. You can try something more challenging:

docker run --rm hello-world

Recent versions of Docker CLI allow the creation of contexts, which allow you to create different contexts for different Docker environments. We can benefit from this to create a specific context for WSL:

docker context create wsl --docker "host=tcp://<YOUR_WSL_IP>:2375"docker context use wsldocker run --rm hello-world

The only problem I have now is that WSL changes instance IP on each reboot, so you have to change the context each time you restart. You can change context like this:

docker context update wsl --docker "host=tcp://<NEW_WSL_IP>:2375"

A simple PowerShell script to update the context automatically so that it is set to the correct host:

$wslip = wsl -- ip -o -4 -json addr list eth0 | ConvertFrom-Json | ForEach-Object { $_.addr_info.local } | Where-Object { $_ }$ctx = docker context ls --format json | ConvertFrom-Json | Where-Object { $_.Name -eq "wsl" }if($null -eq $ctx) {Write-Host "Creating Docker context 'wsl' to host=tcp://$($wslip):2375"docker context create wsl --docker "host=tcp://$($wslip):2375" | Out-Null} else {docker context update wsl --docker "host=tcp://$($wslip):2375" | Out-Null}docker context use wsl | Out-Null

--

--