Docker Bind Mounts in WSL on Windows

Or: how I stopped worrying about slow file operations and love the Linux filesystem

Docker Desktop for Windows

Docker Desktop makes it easy to work with Docker on Windows. When installing, I can choose two “architecture” variants:

  • Hyper-V — requires Windows Pro, so that’s a no-go for my Windows Home.
  • WSL (Windows Subsystem for Linux) — make that WSL 2. This is what I’m using for the remainder of this post. It allows me to run Linux on my 10-year-old Windows Home machine.

    Incidentally, my machine is not officially qualified for Windows 11. However, there appears to be a way to upgrade anyway as Microsoft really really really wants us to get off Windows 10. That’ll be the topic of another post.

After installing and setting up Docker Desktop, things work mostly fine except for bind mounts.

Bind mounts are a way to set up Docker to share a subdirectory from the host OS (in this case Windows) with the container (Linux based image in this case). These types of mounts are very useful for local development when I don’t really care for building images and volumes for deployment (at least not yet). The problem is that mounts set up this way don’t perform well. While they work fine for manual operations, they don’t work well in some use cases.

One such use case is when I have a Next.js project using the “node” base image. Although the build works normally, once I shell in and attempt to start a dev server, I get this warning:

> next dev --turbopack

   ▲ Next.js 15.1.6 (Turbopack)
   - Local:        http://localhost:3000
   - Network:      http://172.18.0.2:3000

 ✓ Starting...
Slow filesystem detected. If /dist is a network drive, consider moving it to a local folder. If you have an antivirus enabled, consider excluding your project directory.
 ✓ Ready in 44.2s

In case you think the warning is not serious, it took 44.2 seconds to start the app (this same app started up in about 3 seconds on my MacBook Pro).

Even if I ignore that, whenever I change the code, the just-in-time compiler just can’t catch up fast enough to reflect the changes. I have to stop and restart the app, waiting again for the 40-second start.

Searching around for this, I found that it has to do with how the WSL (below) works on Windows when dealing with Linux-based images.

WSL

WSL, as mentioned, is the magic that allows running a copy of Linux on Windows. However, if I am working in a Windows command prompt/terminal and using a bind mount, there is some translation going on between the Windows filesystem and the Linux filesystem in the container. And it is that translation that is taking the performance hit.

For more details about this (9P protocol), see this StackOverflow question.

Easy Solution

Copy the Source Files to the Linux Filesystem

The quick-and-easy answer is: move the project from my Windows filesystem (e.g. C:\) to the Linux filesystem that WSL2 uses.

In Windows Explorer, the Linux filesystems are located under the Linux penguin. Each “subdir” under the Linux penguin corresponds to a Linux distribution:

The “docker-desktop” distribution is, as best as I can tell based on this article, best left alone and be treated as internal to Docker.

This means that the project should be moved into the other “subdir” under the penguin. The example above shows that I have Ubuntu-24.04. However, I guess if I used another distribution, that would be shown instead.

Expanding the “Ubuntu-24.04” distribution, the familiar filesystem hierarchy is seen:

If drag-and-drop using the Windows Explorer is your preference, then locate the original project under /mnt/c (a.k.a. C:\). Copy the subdir to somewhere under /home/.../.

If a terminal is your preference, then start up a Linux terminal:

Then the copy:

mkdir -p ~/projects
cp -r /mnt/c/projects/my_project ~/projects

The above will copy the files from the Windows C:\projects\my_project into the Linux’s filesystem at /home/<me>/projects/my_project.

IMPORTANT: remember to skip copying the node_modules, dist, and other potentially large subdirs that are built so that the copy only picks up source files. Otherwise, it’ll be a long wait, and those files will be overwritten eventually anyway.

Work from the Linux Terminal from Now On

Once the files are copied, continue to use the Linux terminal to build and start the container and app therein.

> next dev --turbopack

   ▲ Next.js 15.1.6 (Turbopack)
   - Local:        http://localhost:3000
   - Network:      http://172.22.0.2:3000

 ✓ Starting...
...

 ✓ Ready in 1643ms

There you go: from 44.2 seconds down to 1.6 seconds. And yes–incremental builds are now working correctly.

An Even Easier Solution

Start Fresh and Work from the Linux Terminal from Now On

If the code is in a source control repository (e.g. Git), it’s probably easier to just:

  • commit and push everything.
  • use the Linux command line from the WSL’s Linux distribution environment (and use it from now on for the project).
  • install the source control client (e.g. apt install -y git)
  • checkout from the source control (under the Linux distribution’s filesystem like ~/projects, of course, not under anything in /mnt/) and work from there.

The good thing about this approach is that I don’t have to worry about selecting which files to copy and which files to not copy from Windows.