Setting Up Docker for Windows and WSL to Work Flawlessly

blog/cards/getting-docker-for-windows-and-wsl-working-together.jpg

With a couple of tweaks the WSL (Windows Subsystem for Linux, also known as Bash for Windows) can be used with Docker for Windows.

Quick Jump: Configure Docker for Windows | Install Docker within WSL | Configure WSL to Connect to Docker for Windows | Ensure Volume Mounts Work

This article expects you to have WSL set up already. If you don’t, I have another article that goes over how to set up an amazing WSL based development environment within Windows. You can even run graphical apps and it doesn’t require a VM.

Onwards we go…

While the Docker daemon cannot run directly on WSL, you can use the Docker CLI to connect to a remote Docker daemon running through Docker for Windows or any other VM you create.

It’s really awesome! Using this method, very large Rails applications respond in 200ms (or ~5s when having to compile 10,000+ lines of Javascript and SCSS). That’s with mounted volumes too! In other words, it’s very usable for day to day web development.

Configure Docker for Windows

In the general settings, you’ll want to expose the daemon without TLS. This step is necessary so that the daemon listens on a TCP endpoint. If you don’t do this then you won’t be able to connect from WSL.

blog/docker-for-windows-expose-daemon-without-tls.jpg

You may also want to share any drives you plan on having your source code reside on. This step isn’t necessary but I keep my code on a regular HDD, so I shared my “E” drive too.

Can’t use Docker for Windows?

This is only necessary if you are NOT running Docker for Windows!

No problem, just configure your Docker daemon to use -H tcp://0.0.0.0:2375 and --tlsverify=false. Then you can follow along with the rest of this guide exactly.

If you go down this route, I highly recommend rolling your own VM with VMware Player instead of using the Docker Toolbox because VirtualBox has crazy edge case shared folder bugs that will ruin your life at some point. Don’t worry, VMware Player is free. Just Google how to set up Ubuntu 16 server on VMware Player.

Install Docker within WSL

Everyone can follow along at this point!

We still need to install Docker inside the WSL because it’ll give us access to the Docker CLI. We just won’t bother starting the server.

The following instructions are for Ubuntu but with the 2017 fall update of Windows, WSL now supports a variety of distributions so if you happen to use something other than Ubuntu then follow the Docker installation guide for your distro from Docker’s installation docs.

Here’s the Ubuntu 16 installation notes taken from Docker’s documentation:

This will install the edge channel, change ‘edge’ to ‘stable’ if you want. You may also want to update the Docker Compose version based on the latest release.

# Environment variables you need to set so you don't have to edit the script below.
DOCKER_CHANNEL=edge
DOCKER_COMPOSE_VERSION=1.16.1

# Update the apt package index.
sudo apt-get update

# Install packages to allow apt to use a repository over HTTPS.
sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common

# Add Docker's official GPG key.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# Verify the fingerprint.
sudo apt-key fingerprint 0EBFCD88

# Pick the release channel.
sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   ${DOCKER_CHANNEL}"

# Update the apt package index.
sudo apt-get update

# Install the latest version of Docker CE.
sudo apt-get install -y docker-ce

# Allow your user to access the Docker CLI without needing root.
sudo usermod -aG docker $USER

# Install Docker Compose.
sudo curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose &&
sudo chmod +x /usr/local/bin/docker-compose

At this point you must close your terminal and open a new one so that you can run Docker without sudo. You might as well do it now!

Configure WSL to Connect to Docker for Windows

The next step is to configure WSL so that it knows how to connect to the remote Docker daemon running in Docker for Windows (remember, it’s listening on port 2375).

Open up your ~/.bashrc file and add this line to the bottom:

export DOCKER_HOST=tcp://0.0.0.0:2375.

Logout of your WSL shell and come back in, or run source ~/.bashrc to reload it now.

If you want to get cute, you could do all of that with this 1 liner:

echo "export DOCKER_HOST=tcp://0.0.0.0:2375" >> ~/.bashrc && source ~/.bashrc

You can verify it works by running docker info. You should get back a list of details. If you get a permission denied error then make sure you log out and log back in, because that’s necessary to apply the changes so non-root users can run Docker. That’s what the sudo usermod bit of the long command did in the command chain when installing Docker.

Ensure Volume Mounts Work

The last thing we need to do is set things up so that volume mounts work. This tripped me up for a while because check this out…

When using WSL, Docker for Windows expects you to supply your volume paths in a format that matches this: /c/Users/nick/dev/myapp.

But, WSL doesn’t work like that. Instead, it uses the /mnt/c/Users/nick/dev/myapp format. Honestly I think Docker should change their path to use /mnt/c because it’s more clear on what’s going on, but that’s a discussion for another time.

To get things to work for now, you need to bind a custom mount for any drives that you shared with Docker for Windows.

Bind custom mount points to fix Docker for Windows and WSL differences:
sudo mkdir /c
sudo mount --bind /mnt/c /c

You’ll want to repeat those commands for any drives that you shared, such as d or e, etc..

Verify that it works by running: ls -la /c. You should see the same exact output as running ls -la /mnt/c because /mnt/c is mounted to /c.

At this point you’re golden. You can use volume mount paths like .:/myapp in your Docker Compose files and everything will work like normal. That’s awesome because that format is what native Linux and MacOS users also use.

It’s worth noting that whenever you run a docker-compose up, you’ll want to make sure you navigate to the /c/Users/nick/dev/myapp location first, otherwise your volume won’t work. In other words, never access /mnt/c directly.

Technically you could use a symlink instead of a bind mount, but I’ve been burned in the past when it came to using symlinks and having certain tools not work because they failed to follow them correctly. Better safe than sorry here.

However, feel free to use symlinks inside WSL to access your bind mount. For example my Dev folder lives all the way in /e/Backup/VMs/workstation/home/nick/Dev and there’s no way in heck I’m going to always type that when I want to access my development files.

So inside WSL I created a symlink with ln -s /e/Backup/VMs/workstation/home/nick/Dev ~/Dev and now I can just type cd ~/Dev to access my files and everything works.

Automatically set up the bind mount:

Unfortunately you will have to run that sudo mount command every time you open a new terminal because WSL doesn’t support mounting through the /etc/fstab file yet.

But we can work around that limitation by just mounting it in your ~/.bashrc file. This is a little dirty but as far as I know, I think this is the only way to do it, so if you know of a better way, please let me know.

You can do that with this 1 liner: echo "sudo mount --bind /mnt/c /c" >> ~/.bashrc && source ~/.bashrc and make sure to repeat the command for any additional drives you shared with Docker for Windows. By the way, you don’t need to mkdir because we already did it.

Yes I know, that means you will be prompt for your root password every time you open a terminal, but we can get around that too because Linux is cool like that.

Allow your user to bind a mount without a root password:

To do that, run the sudo visudo command.

That should open up nano (a text editor). Goto the bottom of the file and add this line:
nick ALL=(root) NOPASSWD: /bin/mount, but replace “nick” with your username.

That just allows your user to execute the sudo mount command without having to supply a password. You can save the file with CTRL+O, confirm and exit with CTRL+X.

Mission complete. You’re all set to win at life by using Docker for Windows and WSL.

Let me know how it goes in the comments!

Free Intro to Docker Email Course

Over 5 days you'll get 1 email per day that includes video and text from the premium Dive Into Docker course. By the end of the 5 days you'll have hands on experience using Docker to serve a website.


Comments