Seit einiger Zeit verwende ich privat auf meinen verschiedenen Linuxsystemen Docker. Sei es für diverse Webseiten, Webapps, Mailserver, Minecraftserver etc. Das System hat sich einfach bewährt, weil ich damit die Möglichkeit habe, die einzelnen Anwendungen schnell und ohne großen Aufwand zu sichern oder umzuziehen.
Kürzlich kam ich jetzt in den Genuss, dass ich beruflich Docker unter Windows benötigt habe. Allerdings gibt es unter Windows nur noch offiziell Docker Desktop und dieses ist von den Lizenzen her zu aufwändig, vor allem, wenn man nur kurz was ausprobieren will.
Daher hab ich mich damit beschäftigt, wie man die normale Docker CLI unter Windows verwenden kann. Das ist an sich relativ einfach mit der WSL (windows subsystem for linux), aber man muss eben einiges an Konfigurationsaufwand reinstecken.

WSL installieren

Fangen wir damit an, wie man die WSL installiert. Dafür ist eigentlich nur ein aktuelles Windows 10 oder Windows 11 notwendig und die notwendigen Komponenten sind in der Regel auch schon installiert.
Wir öffnen also eine Kommandozeile mit Administratorrechten (ich verwende mittlerweile immer Windows Terminal mit Powershell) und gibt folgendes ein:

$ wsl --install

Und dann legt das System schon los

Installation: VM-Plattform
VM-Plattform wurde installiert.
Installation: Windows-Subsystem für Linux
Windows-Subsystem für Linux wurde installiert.
Herunterladen: WSL-Kernel
Installation: WSL-Kernel
WSL-Kernel wurde installiert.
Herunterladen: Ubuntu
Der angeforderte Vorgang wurde erfolgreich abgeschlossen. Änderungen werden erst nach einem Neustart des Systems wirksam

Nach dem geforderten Neustart - der kann eine weile dauern, weil jetzt die Windows Features dafür installiert und konfiguriert werden - öffnet sich ein Konsolenfenster mit dem Titel “Ubuntu”. Ubuntu ist das Standardsystem, das für die WSL verwendet wird. Man kann natürlich auch andere Linux-Systeme nachträglich installieren, aber ich konzentriere mich hier jetzt auf den Standard.
In diesem Fenster wird folgendes Angezeigt:

Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username:

Hier gibt man dann die Accountdaten ein, die man künftig für das nun installierte Linuxsystem verwenden will. Ich empfehle, dass man hier den gleichen Benutzernamen verwendet, den man auch unter Windows verwendet. Das minimiert die Verwechselungsgefahr.
Dann sehen wir auch schon den Prompt für das neue Linux-System, das man gerade installiert hat.
Ich habe immer die Angewohntheit, dass ich erst einmal auch gleich das Linuxsystem auf den aktuellen Stand bringe:

$ sudo apt update
$ sudo apt upgrade -y
$ sudo apt autoremove -y

Docker installieren

Dann können wir mal beginnen, dass wir Docker installieren.
An sich kann man Docker auch direkt aus dem Ubuntu-Repository installieren. Aber selbst auf den Docker-Hilfeseiten wird empfohlen, dass man stattdessen das offizielle Docker-Repository einbindet und Docker von da installiert. Damit kann man eher sicherstellen, dass man die aktuellste Version verwendet.
Dazu gibt man folgende Statements - auf die ich jetzt nicht näher eingehe - ein:

$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

Damit ist dann das Repository ins System eingebunden und mit den folgenden Statements kann man dann Docker installieren:

$ sudo apt update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

Damit ist die Installation an sich auch abgeschlossen.

Docker einrichten

Auf einem normalen Linuxsystem wäre jetzt die Installation erledigt und man könnte Docker auch schon verwenden. Wenn man hier aber jetzt versucht Docker zu verwenden, wird es leider noch nicht funktionieren:

$ docker run hello-world

Man erhält dann folgenden Fehler:

docker: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?.
See 'docker run --help'.

Das liegt einfach daran, dass der Docker-Daemon auf einem normalen Linuxsystem im Systemd konfiguriert wird, aber in der WSL ist dieser Sitzungsmanager leider nicht vorhanden (in einer kommenden Version soll das auch in der WSL verfügbar sein).
Man muss also den Docker Daemon manuell starten.
Diesen startet man an sich als root mit dem Kommando dockerd. Das ist allerdings ein bisschen umständlich, wenn man das jedes mal manuell machen muss. Ein geeigneter Ort dafür wäre das Startscript .bashrc.
Daher fügen wir in der .bashrc folgendes am Ende ein:

# Start Docker daemon automatically when logging in if not running.
RUNNING=`ps aux | grep dockerd | grep -v grep`
if [ -z "$RUNNING" ]; then
    echo "docker daemon is launching"
    DOCKER_DIR=/mnt/wsl/shared-docker
    DOCKER_SOCK="$DOCKER_DIR/docker.sock"
    export DOCKER_HOST="unix://$DOCKER_SOCK"
    if [ ! -S "$DOCKER_SOCK" ]; then
        mkdir -pm o=,ug=rwx "$DOCKER_DIR"
        chgrp docker "$DOCKER_DIR"
    fi
    sudo dockerd > /dev/null 2>&1 &
    disown
fi

Jetzt hat man allerdings das Problem, dass dockerd als root ausgeführt werden muss. Durch den sudo Befehl würde man immer, wenn man sich in die WSL einloggt nochmal das Passwort eingeben müssen. Abgesehen davon kann diese zusätzliche Passwortaufforderung dafür sorgen, dass die Anzeige im Terminal fehlerhaft sein kann.
Fügen wir also eine Freigabe in der Sudo-Konfiguration ein, um dieses Problem zu umgehen:

$ sudo visudo

Hier am Ende dann folgendes eingeben:

%sudo ALL(ALL) NOPASSWD: /usr/bin/dockerd

Statt %sudo kann man direkt den Benutzernamen verwenden oder eine andere Gruppe.
Anschließen kann man sich aus dem Linux-System mit exit abmelden und öffnet die WSL nochmal (dazu einfach in der Kommandozeile wsl eingeben).
Jetzt sollte der Docker-Daemon gestartet sein und man kann Docker nocheinmal ausprobieren:

docker run hello-world

Aber auch jetzt bekommt man einen Fehler.

docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/create": dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.

An sich ist das korrekt, weil man auf einem Server docker aus Sicherheitsgründen mit sudo verwenden soll, aber für unsere Zwecke ist das eher hinderlich. Daher wollen wir den aktuellen Benutzer noch der Gruppe docker hinzufügen, damit man docker ohne sudo direkt verwenden kann.

$ sudo usermod -aG docker $USER

Und jetzt sollte endlich alles so funktionieren, wie es soll.

Docker direkt aufrufen ohne die WSL explizit zu starten

Wir haben jetzt Docker in der WSL installiert. Damit man jetzt einen Container starten kann, muss man immer erst in die WSL wechseln und kann dann loslegen. Wem das reicht, der ist jetzt fertig. ich finde es allerdings bequemer, wenn ich direkt auf der Kommandozeile unter Windows gleich meine Docker-Kommandos absetzen kann.
Dazu muss man sich ein bisschen Gedanken machen, wie man ein Kommando in der WSL ausführt:

$ wsl -d Ubuntu ps aux

Damit kann ich zum Beispiel die Prozesse ausgeben, die in der WSL ausgeführt werden.
Man schreibt also nur das Kommando wsl, gefolgt mit dem Parameter -d, der angibt, in welcher Umgebung das Kommando ausgeführt werden soll (man kann ja auch mehrere Linux-Systeme in der WSL installieren) und dann das jeweilige Statement, das man ausführen will.
Das Kommando für docker würde also so aussehen:

$ wsl -d Ubuntu docker -H unix:///mnt/wsl/shared-docker/docker.sock run hello-world

das Socket haben wir im Startscript so definiert und man sollte das entsprechend angeben, damit man voll auf die Docker-Engine zugreifen kann.
Wenn man aber immer das gesamte Statement so eingeben muss, wäre das nicht wirklich eine Erleichterung. Besser wäre es, wenn man auch unter Windows einfach mit dem Befehl docker die entsprechenden Funktionen zur Verfügung hätte.
Das kann man mit Hilfe einer Funktion bewerkstelligen. Eine solche Funktion definiert man am besten im Profile-Skript der Powershell und dieses findet man hier:

C:\Users\<username>\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1

hier schreibt man jetzt also folgendes rein:

function docker {
	wsl -d Ubuntu docker -H unix:///mnt/wsl/shared-docker/docker.sock @Args
}

Das war’s jetzt endlich. Nur noch die Kommandozeile neu öffnen, damit das Profile neu geladen wird und es sollte alles funktionieren.
Wenn wir das Ganze jetzt nochmal unter der Windows Kommandozeile (Powershell) testen, erhalten wir folgende Ausgabe:

$ docker run hello-world

Die Ausgabe sieht dann so aus:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete 
Digest: sha256:18a657d0cc1c7d0678a3fbea8b7eb4918bba25968d3e1b0adebfa71caddbc346
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

Damit sind wir fertig und können Docker jetzt endlich ganz normal unter Windows verwenden.