This is part of a series on moving from desktop Linux back to Windows.
The first post is here. The previous post is here.
Note: Running Docker on Windows requires Windows 10 Pro. The necessary virtualization features are not available on Windows 10 Home.
Table of Contents
This post is juuuust long enough that it probably helps to know what I’ll cover:
- My hopes and expectations for working with Docker on Windows from within WSL
- Docker for Windows in Action (i.e., screenshots of it all working)
- Enabling virtualization
- Installing Docker for Windows
- Options for a Docker client in WSL (by far the longest part of this post)
- docker-compose in WSL
- VSCode Integration
Let’s get started.
Beginning with the end in mind
As I mentioned in the first post in this series, my motivation for moving back to Windows was that I needed a new laptop. It needed to be a solid developer laptop, and nowadays, that means being able to work with Docker.
After a bit of research, I knew:
- I’d need Windows 10 Pro, since the required virtualization features aren’t available on Windows 10 Home
- Docker should work from within Windows Subsystem for Linux (WSL), though I wasn’t sure of the particulars
Ultimately, I wanted the following:
- All
docker
functionality — running public docker images, building and running my own images, etc — from within WSL - All
docker-compose
functionality from within WSL - Easy to keep all docker software up to date and not stuck on old versions
- Practically seamless use of Docker… at least as easy as on Linux, and certainly better than the
docker-machine
rigamarole I had become accustomed to on Mac. - In short: a first-class Docker developer experience
I’m pleased to report that so far, all those expectations have been met. Docker on Windows has been, for me, a joy.
Docker on Windows in Action
Here are some screenshots of Docker on Windows in action:
As a bonus, here’s VS Code Integration:
Enabling Virtualization
Docker for Windows requires virtualization to be enabled, which probably doesn’t happen by default out of the box (it didn’t for me, at any rate).
On this new laptop, I followed these instructions which worked perfectly. On an older PC, they didn’t work and I needed to figure out how to get into the BIOS a different way (Shift-F2 or Shift-F8 at startup, IIRC).
Docker For Windows
Installing Docker for Windows was trivial with chocolatey:
choco install docker-for-windows
Obviously you can install with normal old download-and-double-click-the-exe, as well. Once installed, you can turn all manner of knobs if you need to:
In addition, in general, I’ve found the docs to be fantastic, including the troubleshooting docs.
Docker in WSL
During my research I found 3 separate ways to run Docker client from within WSL connecting to the Docker for Windows daemon:
- Use the Windows Docker client
- Use the Linux Docker client over TCP without TLS
- Use the Linux Docker client with a “relay” between WSL and Windows
Here’s a quick rundown of trade-offs I’ve seen so far for each of these 3 approaches:
Use the Windows Docker Client
Jessie Frazelle explained the seeming-magic of WSL internals in this excellent post. Bottom line: you can simply run the Windows docker.exe (which comes bundled with Docker for Windows) from within WSL, and it works really well.
Here’s an ugly example that uses the full path to docker.exe, just to demonstrate:
If you wanted to stick with this option, you’d want to symlink docker
to that c/Program Files.../docker.exe
so that you can simply run docker
. You’d do that with something like:
sudo ln -s '/mnt/c/Program Files/Docker/Docker/resources/bin/docker.exe' /usr/local/bin/docker
Pros of this approach:
- Easy to do and works great
- Always using a version of the docker and docker-compose clients that match the daemon
- No need to maintain/update docker or docker-compose software in WSL
- Surprisingly (to me), does not set
777
permissions on any files added from a Windows filesystem into the docker image (more on that in a bit)
Cons:
- There are bound to be differences between Windows Docker and Linux Docker clients, though I haven’t found any meaningful ones yet
- As mentioned in the “relay” blog post below, using the Windows Docker client means that it probably wouldn’t match the Linux Docker client
man
pages - Perhaps you have a desire/need/use case for always using the Linux client; for example, maybe you want to guarantee that the behavior you see locally is the same as in your Linux-based CI/CD system (such as Jenkins)
Use the Linux Docker client over TCP without TLS
The next two options use the Linux Docker client rather than the client that ships with Windows for Docker.
If you intend to use the Linux Docker client, do not YOLO apt install docker.io
. Follow the documented Linux client install instructions.
OK, so: for this “TCP without TLS” option, Nick Janetakis has a great blog post on how to use the Linux Docker client from within WSL using the Docker for Windows daemon, and I won’t attempt to recreate any of those instructions here.
Aside from installing docker-ce
from within WSL, it’s otherwise just a 2-step affair that you only need to do once:
- Check a checkbox in the Docker for Windows config screen
- Add an environment variable EXPORT to your WSL ~/.bashrc file
One small note: when I did this, I did need to kill Docker for Windows and restart it after checking the checkbox, because the initial checking seemed to put it into a weird state. No idea whether that’s just a fluke.
Pros of this approach:
- Easy to do, appears to work well
- Using the actual Docker Linux client; behavior should match
man
pages and other usage of a Linux Docker client, such as within a Linux-based CI/CD system (e.g. Jenkins)
Cons:
- That scary “makes you vulnerable to remote code execution attacks. use with caution” language that accompanies the checkbox you check. I really do not know how exploitable this threat vector is… I am not a CISO, lawyer, doctor, rocket surgeon, etc.
- Need to maintain/keep updated Linux Docker client software in addition to the Windows for Docker software
Personally, that first con raises enough of a hackle for me that I won’t use it, especially since this third option, up next, was easy to get going and hasn’t been a nuisance to me in practice.
Use the Linux Docker client with a “relay” between WSL and Windows
A third option — the one I actually started with — is to use the Linux Docker client but without that “TCP without TLS” checkbox. In this approach, you set up a relay between WSL and the Docker for Windows daemon.
In short, in addition to installing the docker-ce
Linux Docker client, it involves:
- One-time creation of the
docker-relay
binary - When working with the Linux Docker client, starting that relay
In addition, I did update my /etc/sudoers
file so that I wouldn’t be prompted for a password every time I ran the relay.
Pros of this approach:
- Easy (ish) to do; works great
- Using the actual Linux Docker client; behavior should match
man
pages and other usage of a Linux Docker client, such as within a Linux-based CI/CD system (eg Jenkins) - Doesn’t suffer from the potential security vulnerability of the TCP without TLS approach, above
Cons:
- Have to remember to start the relay when working with the Linux Docker client (or auto-start it somehow when opening WSL)
- Need to maintain/keep updated Linux Docker client software in addition to the Windows for Docker software
I’ve been using this option for a few months and it’s worked fine. Remembering to start the relay hasn’t been a nuisance in practice
A note on file permissions
Caveat: This might not matter to you at all!
I mentioned above that when doing docker build
using the Windows Docker client, any files added from a Windows filesystem to the docker image do not get 777
permissions. In addition, the Docker client issues a warning about file system permissions (more details here.) Which begs the question: “Why on earth would you suspect that they would get 777
permissions?”
The answer is that because when you docker build
from within WSL using the Linux client, any files you add do get 777
permissions.
For example, I keep all my development projects on the Windows filesystem, starting at c:\dev\projects
. And from within WSL, I access them from /c/dev/projects
. Yes, that means even from within WSL, I’m working on a Windows filesystem for all dev projects. If you list those files, you’ll see that everything gets world permissions (i.e. 777
).
And when you build an image from the Linux client, if your stuff is on the Windows file system, any files that get added will by default retain those world permissions. Here’s an example, one after building with the Linux client, and one after building with the Windows client. The entrypoint.sh
file is set to ls -la /entrypoint.sh
so that you can easily see an example of the file permissions that I’m talking about:
After building with Linux client:
After building with Windows client:
This might not affect you if you’re building images whose Dockerfile is on the Linux file system within WSL. It might not matter to you at all. Or you may choose to just update your Dockerfile to explicitly set permissions on any files/directories that get added to the docker image.
Docker Compose
All of the options above for having the Docker client communicate with the Docker for Windows daemon apply to docker-compose
.
If you choose to stick with using the Windows clients, you’d just want to symlink the Windows docker-compose.exe to docker-compose
, similar to the docker.exe symlink shown above.
And if you choose to go with the Linux client, be sure to follow the documented instructions for installing docker-compose.
VSCode Integration
For VSCode integration:
- Install the Docker extension (
ctrl-shift-x
, search “docker”, Install) - Optionally, if you have them, Plug in your Docker Hub credentials if you want to navigate images that you’ve pushed to Docker Hub from within VS Code
Here’s that image again from above. Note the GUI panels on the left that list images and containers, and note the terminal integration underneath the editors.
This is interesting to me: regardless of what Docker client option you go with for how you interact with the Windows for Docker Daemon, VSCode is going to use the Windows client for its GUI integrations, such as listing images and containers. However, for interactions with those items — such as right-clicking an image and running it or attaching to a running container — it’s going to use whatever shell you have configured VSCode to use by default. So in the example above, note that I have configured it to use Bash (via WSL). Consequently, interacting with those images and containers from that configured shell are going to use whatever Docker client option you choose from the options above.
Wrapping up
When I embarked upon this Goin-back-to-Windows experiment, I knew that Docker would be a kind of bellwether for me. If it worked how I hoped it would, then most likely I figured this experiment would overall be a pretty big success. And if it was janky and felt second-class, then most likely I’d end up ditching the experiment and dual-booting a Linux distro onto this new laptop.
I am, so far, very happy with the Docker experience on Windows.
Perfect timing. Just got notice my Win10 machine will be here by the end of the week 🙂