Install Docker on Windows (WSL) without Docker Desktop
Tags: docker, devops, wsl, windows
Published: 2021-02-14 21:18:59

> Updated April 10, 2022, with current Alpine instructions, Debian/Ubuntu package signing tweaks (no more apt-key), and better guidance for handling iptables in Debian. A little more suggestion about TCP access, as well. And further emphasis on the optional nature of the `/mnt/wsl/shared-docker` socket directory.

Windows Subsystem for Linux 2 sports an actual Linux kernel, supporting real Linux containers and Docker. Docker works on WSL 2, and _without_ requiring the robust but heavy Docker Desktop if that is undesirable. However, due to both WSL and Docker complexities, a little tender loving care is required to get Docker up and running. This article attempts to explore such a process and options along the way.

Contrary to what the length of this article might suggest, getting Docker working on WSL is fairly simple. In a nutshell:

- Instead of using an init system such as systemd to launch the Docker daemon, launch it by calling `dockerd` manually.
- If sharing the Docker daemon between WSL instances is desired, configure it to use a socket stored in the shared `/mnt/wsl` directory.
- If sharing and privileged access without sudo are desired, configure the `docker` group to have the same group ID across all WSL instances
- For simplicity, rather than launch a Windows-based Docker client, launch `docker` inside WSL.

Plenty more nuance and decisions below, of course. See details regarding the [companion Github repo][repo] by scrolling to the bottom.

## Are you sure you don't want Docker Desktop?

Before proceeding, let's note that Docker Desktop is amazing. If you want Docker to work on Windows and WSL 2, [installing Docker Desktop][dockerdesktop] is most likely the way to go. With [Docker Desktop's WSL 2 backend][wsl2backend], Docker integrates with Windows in a fairly elegant way, and the docker client can be launched from either Powershell or Linux. [Reading about what goes on under the hood][wsl2backendexplained] is an entertaining and informative endeavor, as well. Very clever.

If you came here looking how to get Docker running easily, or if you want Windows containers (still a rarity) out of the box, then Docker Desktop is your friend, and you can [go install it][dockerdesktop] now.

But if you, like me, feel that all the added complexity of Docker Desktop is unnecessary, you don't need Windows containers, or you are simply tired of that whale in the system tray taking... so... long... then perhaps you want to run the docker daemon (`dockerd`) in the WSL distro of your choice and be happy. You are at the right place.

> Note that Docker Desktop is only free individuals or for small companies. If you are using it for work, and your company exceeds a certain size or revenue, then consider paying for a subscription. Of course, if you use Docker without Docker Desktop, as detailed in this article, then this does not apply. [See more details about the Docker subscription model here](https://www.docker.com/blog/the-grace-period-for-the-docker-subscription-service-agreement-ends-soon-heres-what-you-need-to-know/).

## Have you tried Podman?

[![Podman](https://dev-to-uploads.s3.amazonaws.com/i/s8ol1ckerpnl582s6egd.png)][podman]

Before we mosey along, though: are you aware of [Podman]? Podman is daemonless (no background service needed), modern (cgroups v2 out of the box), supports rootless, and serves as a drop-in replacement for Docker. Without needing to worry about sockets and ports, a lot of headaches go away.

[I have written about getting Podman to work on WSL 2][podmanwsl]. Feel free to try it out. You may never look back. And, yes, VSCode can work with podman.

And yet... Sometimes, one just needs Docker to work. Maybe some tooling you use can't handle Podman, or you just want to put WSL through its paces. If so, read on.

## Make sure that WSL is version 2

Please note that these steps require WSL 2 (not version 1). WSL 2 uses an actual Linux kernel that allows Linux containers. WSL 1 was genius with running Linux on the Windows kernel, but of course lacked some of the features, such as containers. Microsoft offers a [more detailed comparison in the docs][wsl1vs2].

__You will most certainly need WSL 2 to run the Docker service.__

Does the command `wsl --set-default-version 2` work? This will set the default version to WSL 2, or fail if you are still on the first version.

[Microsoft's has step-by-step instructions on how to upgrade to WSL 2](https://docs.microsoft.com/en-us/windows/wsl/install-win10#step-2---update-to-wsl-2).

To run WSL 2, Windows version 1903 or higher is needed, with Build 18362 or higher. To tell what version you are running, run `winver` in Powershell or CMD, or just type Win key and R (⊞-r) to open the Run dialog and then enter `winver`. Hopefully you will see something like "Version 21H2. OS Build 19044.1586"

## Install a Linux distro

If you do not yet have a running WSL instance with a distro of your choice, the next step is to [pick one from the Microsoft Store][wslstore]. If you dislike the Windows Store, [there are other options](https://docs.microsoft.com/en-us/windows/wsl/install-manual#downloading-distros).

Custom installations are also a great option with WSL 2. For instance, [install and configure Fedora][fedorawsl], or any other distro for which you can obtain a rootfs in tar format and then `wsl --import rootfs.tar`.

This guide includes instructions for launching dockerd in [Debian][debianwsl], [Ubuntu][ubuntuwsl], [Alpine][alpinewsl], and [Fedora][fedorawsl]. If you think there is another obvious WSL distro that should be considered, feel free to let me know in the comments.

## Configure a non-root user

Once you have installed the distro of your choice, launch it and set up a non-root user if you have not already. Debian and Ubuntu will configure this automatically at first launch, as should Alpine if you installed it from the Store. If the `whoami` command returnes "root", then you will want to add a non-root user. For Alpine or Fedora, use `adduser myusername` to create a new user. On Alpine, this should prompt for the new password. On Fedora, you will additionally need to `passwd myusername` and enter the password you want to use. (If your Fedora does not have `passwd`, then you will need to first `dnf install passwd cracklib-dicts`).

On later versions of Alpine from the Microsoft Store, while a non-root user is created as part of setup, this user is initially password-less. You can double check on any distro with:

```
cat /etc/shadow | grep myusername | cut -d: -f2
```

(If you are not root, you may need to `su` first).

If the result is "!" then that user has no password set. If the result is a random hash string, then you are good. If you need to set a password, you can use `passwd myusername` (of course, in all of the above, use your username in place of "myusername."

## Configure admin (sudo) access for the non-root user

If you used Debian or Ubuntu from the Windows store and set up the default user on first launch, then sudo should already be configured on behalf of the default user. You can skip this step, and proceed to updating packages and testing network connectivity, below.

When signed in as the user you set up (try `su myusername` if you are still root), can you `sudo -v` without an error?

If not, first make sure that sudo is installed. On Alpine, that's `apk add sudo` and on Fedora, `dnf install sudo`. If this fails due to network connectivity, see below. You can follow the directions there in order to correct DNS, but of course eliminate any occurrence of `sudo` in those commands, as you do not have it yet, and you should still be root anyway.

Is your user a "sudoer"? Try the following to see if they are part of the `sudo` or `wheel` group:

```sh
grep -E 'sudo|wheel' /etc/group
```

On distros that have a `sudo` group, such as Ubuntu and Debian, you should see something like `sudo:x:27:myusername` and on distros that have a `wheel` group, such as Fedora and Alpine, you should see something like `wheel:27:myusername`.

If your username is missing from the group, take note of the group name (`sudo` or `wheel`) and add the user in question to that group:

- Alpine: `addgroup myusername wheel`
- Fedora: `usermod -aG wheel myusername`
- Probably not necessary, but on Ubuntu/Debian: `usermod -aG sudo myusername`

Finally, as root, make sure that the admin group (whether `sudo` or `wheel`) is enabled for sudo:

```sh
grep -E '%sudo|%wheel' /etc/sudoers
```

If the line is there, but commented out with a `# `, then run `visudo` then make sure the line reads thus (use `wheel` or `sudo` as determined earlier):

```
%wheel ALL=(ALL) ALL
```

Then save.

Once these steps are complete, test again with:

```sh
su myusername
sudo -v
```

If you are prompted for the password, then all is well. If you instead received an error containing something like "Sorry, user myusername may not run sudo" then you may need to follow the steps again, from the beginning.

## Set default user

If you obtained your Linux distro from the Store, you can likely skip this step, as the default user is already set up.

If, however, when you launch WSL, you are still root, then set your new user as the default.

Assuming you have Windows build 18980 or later: simply add a [user section to `/etc/wsl.conf`](https://docs.microsoft.com/en-us/windows/wsl/wsl-config#user).

Something like this will work well if you do not already have that file, or a `[user]` section in it:

```bash
printf "\n[user]\ndefault = myusername\n" | sudo tee -a /etc/wsl.conf
```

However, if on a version of Windows before build 18980, then you will instead need to edit the registry to set a default user. Before doing this, we will need two bits of information: the user id, and the name of the WSL distro. Chances are, you already know these. If not, you can obtain the user id with `id -u myusername` and check your list of WSL distros with (in Powershell) `wsl -l`

Then, use the following command in Powershell, but use your WSL distro name in place of "Alpine" and use your user id in place of "1000":

```powershell
Get-ItemProperty Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Lxss\*\ DistributionName | Where-Object -Property DistributionName -eq Alpine  | Set-ItemProperty -Name DefaultUid -Value 1000
```

Whichever method you use, test by logging out of WSL, and then log back in. Confirm that `whoami` yields the correct username. Success.

## Update/upgrade packages and test network connectivity

Let's make everything new and shiny with one of the following:

- Debian/Ubuntu: `sudo apt update && sudo apt upgrade`
- Fedora: `sudo dnf upgrade`
- Alpine: `sudo apk upgrade -U`

## Network issues?

Upgrading the packages also serves as a network test. For a variety of reasons, network connectivity issues [can happen](https://github.com/microsoft/WSL/issues?q=is%3Aissue+label%3Anetwork) with WSL 2, and tweaking the DNS settings often resolves these problems in my experience. If the upgrade command succeeded, you can skip this section. But if the above commands fail to access the package servers, it may be something unique to your network, or your firewall or anti-malware software. I recommend the following:

```sh
echo -e "[network]\ngenerateResolvConf = false" | sudo tee -a /etc/wsl.conf
sudo unlink /etc/resolv.conf
echo nameserver 1.1.1.1 | sudo tee /etc/resolv.conf
```

The first line tells WSL to cease auto-configuring the `/etc/resolv.conf` file. Then we remove/unlink the old file, and create a new one.

With this newly-configured DNS resolver (in this case, pointing directly to Cloudflare's DNS server) you can try upgrading packages again. Success? Excellent.

## Prepare for Docker installation

Thankfully, there are [official guides for installing Docker](https://docs.docker.com/engine/install/) on various Linux distributions. I have based these instructions on those, with some tweaks learned from real world testing.

### Remove residue

If this is not a fresh install, and you may have experimented with docker before, then first clear out any residual docker installs:

- Fedora: `sudo dnf remove moby-engine docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine`
- Debian/Ubuntu: `sudo apt remove docker docker-engine docker.io containerd runc`
- Alpine (probably not necessary, but just in case): `sudo apk del docker-cli docker-ce docker-openrc docker-compose docker`

### Install dependencies

Then, install pre-requisites:

- Debian/Ubuntu: `sudo apt install --no-install-recommends apt-transport-https ca-certificates curl gnupg2`
- Fedora: `sudo dnf install dnf-plugins-core`
- Alpine: Nothing needed. Dependencies will be installed later, automatically.

### Debian: switch to legacy iptables

Docker [utilizes iptables to implement network isolation](https://docs.docker.com/network/iptables/). For good reason, [Debian uses the more modern nftables](https://wiki.debian.org/nftables), but this means that Docker cannot automatically tweak the Linux firewall. Given this, you probably want to configure Debian to use the legacy iptables by default:

```
update-alternatives --config iptables
```

And select iptables-legacy.

If you are comfortable, instead, with nftables and want to configure nftables manually for Docker, then go for it. I suspect that most, however, will want to switch to iptables legacy.

### Debian/Ubuntu package repository configuration

On Debian or Ubuntu, first temporarily set some OS-specific variables:

```sh
. /etc/os-release
```

Then, make sure that `apt` will trust the repo:

```sh
curl -fsSL https://download.docker.com/linux/${ID}/gpg | sudo tee /etc/apt/trusted.gpg.d/docker.asc
```

`ID` will be either "ubuntu" or "debian", as appropriate, depending on what is in `/etc/os-release`.

Then add and update the repo information so that `apt` will use it in the future:

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

### Fedora package repository configuration

On Fedora, first add Docker's repo:

```sh
sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo
```

## Install Docker

Now we can install the official Docker Engine and client tools:

- Debian/Ubuntu: `sudo apt install docker-ce docker-ce-cli containerd.io`
- Fedora: `sudo dnf install docker-ce docker-ce-cli containerd.io`
- Alpine (install the latest from edge): `sudo apk add docker --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community`

## Add user to `docker` group

The Docker daemon is a service that Docker requires to be running in the background. The service (`dockerd`) and client (`docker`) communicate over a socket and/or a network port. For communication over the socket, privileged access is required. Two ways to obtain this access:

- Run `docker` as root (i.e. `sudo docker`)
- Through group membership, grant specific users privileged access to the Docker socket

In other words, unless you want to utilize `sudo` or root access every time, add your user to the Docker group, named `docker`:

- Fedora/Ubuntu/Debian: `sudo usermod -aG docker $USER`
- Alpine: `sudo addgroup $USER docker`

Then close that WSL window, and launch WSL again. You should see `docker` when you run the command `groups` to list group memberships.

## Sharing dockerd: choose a common ID for the `docker` group

If you only plan on using one WSL distro, this next step isn't strictly necessary. However, if you would like to have the option of sharing the Docker socket system-wide, across WSL distributions, then all will need to share a common group ID for the group `docker`. By default, they each may have a different ID, so a new one is in order.

First, let's pick one. It can be any group ID that is not in use. Choose a number [greater than 1000](https://en.wikipedia.org/wiki/Group_identifier#Reserved_ranges) and [less than 65534](https://en.wikipedia.org/wiki/Group_identifier#Type). To see what group IDs are already assigned that are 1000 or above:

```sh
getent group | cut -d: -f3 | grep -E '^[0-9]{4}' | sort -g
```

Can't decide what number to use? May I suggest 36257. (Just dial DOCKR on your telephone keypad...) Not likely to be already in use, but check anyway:

```sh
getent group | grep 36257 || echo "Yes, that ID is free"
```

If the above command returns a line from `/etc/group` (that does not include `docker`), then pick another number and try again. If it returns "Yes, that ID is free" then you are good to go, with the following:

```sh
sudo sed -i -e 's/^\(docker:x\):[^:]\+/\1:36257/' /etc/group
```

Or, if `groupmod` is available (which it is on Fedora, Ubuntu, and Debian, but not Alpine unless you `sudo apk add shadow`), this is safer:

```sh
sudo groupmod -g 36257 docker
```

Once the group id has been changed, close the terminal window and re-launch your WSL distro.

_Note that the above steps involving the `docker` group will need to be run on any WSL distribution you currently have or install in the future, if you want to give it access to the shared Docker socket._

## (Optionally) prepare a shared directory

As with the last step, if you only plan on using one WSL distro, this next step isn't strictly necessary. However, if you would like to have the option of sharing the Docker socket system-wide, across WSL distributions, then a shared directory accessible to all is needed.

Let's first make a shared directory for the docker socket, and set permissions so that the docker group can write to it.

```sh
DOCKER_DIR=/mnt/wsl/shared-docker
mkdir -pm o=,ug=rwx "$DOCKER_DIR"
chgrp docker "$DOCKER_DIR"
```

## Configure `dockerd` to use the shared directory

Again, this step can be skipped if you opt against using a shared directory for the docker socket. However, you may have other settings you wish to put in `daemon.json`, so you may appreciate some familiarity with this topic.

I suggest using the configuration file `/etc/docker/daemon.json` to set dockerd launch parameters. If the `/etc/docker` directory does not exist yet, create it with `sudo mkdir /etc/docker/` so it can contain the config file. Then the following, when placed in `/etc/docker/daemon.json`, will set the docker host to the shared socket:

```json
{
  "hosts": ["unix:///mnt/wsl/shared-docker/docker.sock"]
}
```

## Launch `dockerd`

Most Linux distributions use systemd or other init system, but WSL has its own init system. Rather than twist things to use the existing init system, we just launch `dockerd` directly:

```sh
sudo dockerd
```

There should be several lines of info, warnings related to `cgroup blkio`, and the like, with something like `API listen on /mnt/wsl/shared-docker/docker.sock` at the end. If so, you have success.

Open another wsl terminal.

If and only if you opted to use the shared docker socket in `/mnt/wsl/shared-docker` as detailed above, first set the `DOCKER_HOST` environment variable:

```sh
export DOCKER_HOST="unix:///mnt/wsl/shared-docker/docker.sock"
```

Then, try out the docker cli:

```sh
docker run --rm hello-world
```

You should see the "Hello from Docker!" message.

## Launch script for `dockerd`

The following lines can be placed in `.bashrc` or `.profile` if autolaunching is desired, or in a separate shell script. For instance, you may want to create a script `~/bin/docker-service` so that you can run `docker-service` only when you want, manually.

```sh
DOCKER_DISTRO="Debian"
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"
    /mnt/c/Windows/System32/wsl.exe -d $DOCKER_DISTRO sh -c "nohup sudo -b dockerd < /dev/null > $DOCKER_DIR/dockerd.log 2>&1"
fi
```

If you went with the default docker socket location of `/var/run/docker.sock` instead of the shared socket directory of `/mnt/wsl/shared-docker` as detailed above, then the script can be something like this:

```sh
DOCKER_DISTRO="Debian"
DOCKER_LOG_DIR=$HOME/docker_logs
mkdir -pm o=,ug=rwx "$DOCKER_LOG_DIR"
/mnt/c/Windows/System32/wsl.exe -d $DOCKER_DISTRO sh -c "nohup sudo -b dockerd < /dev/null > $DOCKER_LOG_DIR/dockerd.log 2>&1"
```

You may choose whatever location you would like for your docker logs, of course. It just needs to be in a place that has permissions so that your user can write to it.

Note that `DOCKER_DISTRO` should be set to the distro you want to have running `dockerd`. If unsure of the name, simply run `wsl -l -q` from Powershell to see your list of WSL distributions. Pick the right one and set it to `DOCKER_DISTRO`.

The script above does the following:

- Sets the environment variable `$DOCKER_HOST` to point to the shared socket. This isn't necessary for `dockerd` but it will allow running `docker` without needing to specify `-H unix:///mnt/wsl/shared-docker/docker.sock` each time.
- Checks if the `docker.sock` file already exists and is a socket. If it is present, do nothing. If not present then the script does the remaining steps, as follows.
- Creates the shared docker directory for the socket and `dockerd` logs, setting permissions appropriately
- Runs `dockerd` _from the specified distro_. This is important, because it allows any WSL distro to launch `dockerd` if it isn't already running.
- When `dockerd` is launched, pipe its output and errors to a shared log file.
- When `dockerd` is launched, it runs in the background so you don't have to devote a terminal window to the process as we did earlier. The `sudo -b` flag gives us this, and we run with `nohup` so that it runs independent of the terminal, with an explicit null input to `nohup` to avoid extra warnings. Both standard output and errors are written to the logfile, hence the `2>&1` redirect.

## Passwordless launch of `dockerd`

If the above script is placed in `.bashrc` (most Linux distros) or `.profile` (distros like Alpine that have Ash/Dash as the default shell), or other shell init script, then it has an unfortunate side effect: you will likely be prompted for a password most every time a new terminal window is launched.

To work around this, you can, if you choose, tell `sudo` to grant passwordless access to `dockerd`, as long as the user is a member of the `docker` group. To do so, enter `sudo visudo` and add the following line (if your `visudo` uses `vi` or `vim`, then be sure to press "`i`" to begin editing, and hit ESC when done editing):

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

Save and exit ("`:wq`" if the editor is `vi`, or Ctrl-x if it is `nano`), and then you can test if `sudo dockerd` prompts for a password or not. For peace of mind, you can double-check: something like `sudo -k ls -a /root` should still require a password, unless the password has been entered recently.

## Make sure `$DOCKER_HOST` is always set

If using the script earlier to launch `dockerd`, then `$DOCKER_HOST` will be set, and future invocations of `docker` will not need an unwieldy `-H unix:///mnt/wsl/shared-docker/docker.sock`

If that script is already in your `.bashrc` or `.profile`, then the following is unnecessary. If, however, you manually invoke `dockerd` in some way, then the following may be desirable in your `.bashrc` or `.profile`, if you opted for the shared docker socket directory:

```sh
DOCKER_SOCK="/mnt/wsl/shared-docker/docker.sock"
test -S "$DOCKER_SOCK" && export DOCKER_HOST="unix://$DOCKER_SOCK"
```

The above checks for the docker socket in `/mnt/wsl/shared-docker/docker.sock` and, if present, sets the `$DOCKER_HOST` environment variable accordingly. If you want a more generalized "if this is wsl, then set the socket pro-actively" then you may prefer the following, which simply check for the existence of a `/mnt/wsl` directory and sets the docker socket if so:

```sh
[ -d /mnt/wsl ] && export DOCKER_HOST="unix:///mnt/wsl/shared-docker/docker.sock"
```

## Running `docker` from Windows

If configured as above, I recommend always running docker from wsl. Do you want to run a container? Do so from a WSL window. ([See my article on using Windows Terminal](https://dev.to/bowmanjd/use-the-new-windows-terminal-4ll7) for a convenient way to use WSL and Powershell.)

This doesn't just apply to the terminal, either. For instance, [VSCode supports docker in WSL 2](https://code.visualstudio.com/blogs/2020/07/01/containers-wsl).

But if you want the convenience and utility of running docker in a Powershell window, I have a couple suggestions.

One is to expose `dockerd` over a TCP Port, or, better yet, set up an SSH server in WSL and connect that way. `docker context` will likely be your friend. Such methods will be explored in a later article, but I encourage you, reader, to explore. A hint: ever tried [scoop.sh](https://scoop.sh/)? After setting it up, `scoop install docker docker-compose` will get you some familiar tools, then an SSH server such as Dropbear or OpenSSH on the WSL side...

A simplified method I recommend: a Powershell function that calls the WSL `docker`, passing along any arguments. This function can be placed in your Powershell profile, usually located at `~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1`

```powershell
$DOCKER_DISTRO = "fedora"
function docker {
    wsl -d $DOCKER_DISTRO docker -H unix:///mnt/wsl/shared-docker/docker.sock @Args
}
```

Again, try `wsl -l -q` to see a list of your WSL distributions if you are unsure which one to use.

Make sure the Docker daemon is running, then launch a new Powershell window, and try the `hello-world` container again. Success?

You could also make a batch file with the appropriate command in it. For instance, name it `docker.bat` and place in `C:\Windows\system32` or other location included in `%PATH%`. The following contents will work in such a script:

```bat
@echo off
set DOCKER_DISTRO=fedora
wsl -d %DOCKER_DISTRO% docker -H unix:///mnt/wsl/shared-docker/docker.sock %*
```

You could go a step further and ensure that `dockerd` is running whenever you start Powershell. Assuming that the `dockerd` start script detailed above is saved in a file in WSL as `$HOME/bin/docker-service` and is executable (try `chmod a+x $HOME/bin/docker-service`), then the following line in your Powershell profile will launch `dockerd` automatically:

```powershell
wsl -d $distro ~/bin/docker-service
```

Not sure where your Powershell profile is located? Try entering `$profile` in a powershell window.

If you don't want to rely on a particular WSL shell script, you could implement a Powershell function to launch dockerd, such as this:

```powershell
function Docker-Service {
  Param ([string]$distro)
  $DOCKER_DIR = "/mnt/wsl/shared-docker"
  $DOCKER_SOCK = "$DOCKER_DIR/docker.sock"
  wsl -d "$distro" sh -c "[ -S '$DOCKER_SOCK' ]"
  if ($LASTEXITCODE) {
    wsl -d "$distro" sh -c "mkdir -pm o=,ug=rwx $DOCKER_DIR ; chgrp docker $DOCKER_DIR"
    wsl -d "$distro" sh -c "nohup sudo -b dockerd < /dev/null > $DOCKER_DIR/dockerd.log 2>&1"
  }
}
```

This function takes one parameter: the distro name.

In all of the above, the principle is the same: you are launching Linux executables, using [WSL interoperability](https://docs.microsoft.com/en-us/windows/wsl/interop).

## A note on bind mounts: stay on the Linux filesystem

With docker, it is possible to mount a host system's directory or files in the container. The following often works, but is _not_ advisable when launching WSL docker from Windows:

```sh
echo "<h1>Hello, World</h1>" > index.html
docker run -p "8080:80" -v "$PWD:/usr/share/nginx/html:ro" nginx
```

Instead of doing the above haphazardly, when launching WSL docker from Powershell, two recommendations:

1. For performance reasons, only bind mount from within the Linux filesystem. To get to a Linux directory while in Powershell, try something like `cd (wsl wslpath -m ~/path/to/my/dir)`
2. Instead of `$PWD` to get the current directory, try `'$(wslpath -a .)'` and, yeah, the single quotes are helpful for escaping.

An example:

```powershell
cd (wsl wslpath -m ~)
mkdir html
cd html
echo "<h1>Hello, World</h1>" > index.html
docker run -p "8080:80" -v '$(wslpath -a .):/usr/share/nginx/html:ro' nginx
```

Then point your browser to http://localhost:8080, and happiness will result. (Depending on [your network configuration](https://docs.microsoft.com/en-us/windows/wsl/compare-versions#accessing-a-wsl-2-distribution-from-your-local-area-network-lan), you may instead need to access this through http://[WSL IP Address]:8080 which should be obtainable with `ifconfig` or `ip addr`)

Try `wsl wslpath` from Powershell, or just `wslpath` from Linux, to see the options. This is a very useful tool, to say the least.

## Example scripts

After walking through the steps in this article, you should now have a working and potentially auto-launched `dockerd`, shared Docker socket, and conveniently configured `docker` command.

Some of the code examples above have been placed in scripts in [a companion Github repo][repo]. I summarize the files available here:

- [docker-service.sh](https://github.com/bowmanjd/docker-wsl/blob/main/docker-service.sh) is a Unix shell script to launch `dockerd`
- [docker-service.ps1](https://github.com/bowmanjd/docker-wsl/blob/main/docker-service.ps1) contains a Powershell function to launch `dockerd` in WSL
- [docker.bat](https://github.com/bowmanjd/docker-wsl/blob/main/docker.bat) is a Windows batch file for launching WSL `docker` from CMD
- [docker.ps1](https://github.com/bowmanjd/docker-wsl/blob/main/docker.ps1) contains a Powershell function for launching WSL `docker` from Powershell, if placed in your profile

No doubt there are ways these can be tweaked to be more useful and reliable; feel free to post in the comments.

## Interested in further tinkering with WSL 2?

Other articles I have written on WSL:

- [Using podman instead of Docker](https://dev.to/bowmanjd/using-podman-on-windows-subsystem-for-linux-wsl-58ji)
- [You Probably Don't Need systemd on WSL](https://dev.to/bowmanjd/you-probably-don-t-need-systemd-on-wsl-windows-subsystem-for-linux-49gn)
- [Install Fedora on WSL](https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26)
- [How to Upgrade from Fedora 32 to Fedora 33](https://dev.to/bowmanjd/how-to-upgrade-from-fedora-32-to-fedora-33-on-windows-subsystem-for-linux-wsl-144e)

[alpinewsl]: https://www.microsoft.com/en-us/p/alpine-wsl/9p804crf0395
[debianwsl]: https://www.microsoft.com/en-us/p/debian/9msvkqc78pk6
[dockerdconfig]: https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-configuration-file
[dockerdesktop]: https://docs.docker.com/docker-for-windows/install/
[dockerdmanualstart]: https://docs.docker.com/config/daemon/#start-the-daemon-manually
[expandvhd]: https://docs.microsoft.com/en-us/windows/wsl/compare-versions#expanding-the-size-of-your-wsl-2-virtual-hard-disk
[fedorawsl]: https://dev.to/bowmanjd/install-fedora-on-windows-subsystem-for-linux-wsl-4b26
[podman]: https://podman.io/
[podmanwsl]: https://dev.to/bowmanjd/using-podman-on-windows-subsystem-for-linux-wsl-58ji
[repo]: https://github.com/bowmanjd/docker-wsl
[ubuntuwsl]: https://ubuntu.com/wsl
[wsl1vs2]: https://docs.microsoft.com/en-us/windows/wsl/compare-versions
[wsl2backend]: https://docs.docker.com/docker-for-windows/wsl/
[wsl2backendexplained]: https://www.docker.com/blog/new-docker-desktop-wsl2-backend/
[wslstore]: https://aka.ms/wslstore