Using Visual Studio Code Remote Explorer with WSL 2 Will Change How You Think About Volume Bind Mounts


On Visual Studio Code + WSL: Running PHP Intellisense from Alpine Linux on Windows 10, I wrote at the end of last month (January 28, 2020) about:

  • Debugging the source code mounted from Windows to Docker Desktop by Ubuntu WSL, using Visual Studio Code, and an PHP interpreter located in Alpine WSL.

Pre Docker Desktop 2.2, this was a great workflow because it created separation of concerns:

  • Ubuntu WSL version 1 contained Docker Compose, and the Docker Desktop daemon TCP connection.
  • Alpine WSL version contained PHP only, and only PHP.
  • The source code in Windows was visible to both Alpine and Ubuntu via Windows to WSL mounts.
  • The source code in Windows was visible to the Docker Desktop engine via Docker Compose mounts from Windows to Docker.

Using Visual Studio Code’s Remote Explorer, I would open a connection to the Alpine WSL version 1 distribution, and it would have visibility into the same source code mounted from Windows into the Docker Engine by the other Ubuntu WSL distribution. Here’s a diagram of the WSL version 1 architecture:

Separation of concerns between distributions under WSL version one, has low bind volume mount performance*


It looks complicated but it’s really not: it’s just one source of truth for the source code (Windows), and three mount destinations. Two destinations have discrete task: 1) Run the code using PHP inside a Docker container (Docker Daemon, inside Hyper V), 2) Analyze the code using the PHP interpreter to assist in editing (Alpine WSL). The third destination, WSL Ubuntu didn’t do anything with the code. It was just mounted there automatically by the WSL system (could also be turned off via /etc/wsl.conf).


Well, how things can change in just a few days. Three days later after writing that January 28th article, I migrated my Windows stack to Docker Desktop and Windows Subsystem for Linux version 2 (January 31st). Holy Keanu C. Reeves, talk about a moving target.

With my migration from WSL version 1 to version 2, performance is a game changer. But it comes with it’s own set of considerations regarding bind volume mount workflow:

  • Windows to Docker Desktop source code bind volume mounts are out.
  • Windows Subsystem for Linux to Docker Desktop bind mounts are in.

Why? On my WSL 2 migration article I go on a research day trip, replete with original sources and benchmark references, explaining how mounting from WSL version 2 (version two specifically) to Docker Desktop 2.2 is essentially mounting code from one Linux distribution (WSL) to another Linux distribution (Docker Desktop 2.2, running inside WSL). Also, Hyper V is out.

What all this means is that the January 28 article’s workflow of mounting source code from Windows into Docker Desktop is now discouraged, if you want to take advantage of Docker Desktop 2.2’s near native Linux performance anyways.

And let’s be honest, who doesn’t want native Linux performance? The name of the game with the new stack is WSL to WSL mounts. Specifically WSL [insert your user distro here] to Docker Engine WSL distro.

Let me show you something:

Docker Engine running as a WSL distro. Both Windows and WSL native mounts are available to Docker Engine.

You see these two little guys chillin’ over here?

That’s the Docker Engine. The Docker Engine is sitting inside it’s own WSL distro.

Here’s a diagram of my take on the architecture, as it relates to me:

Bind mount volume communication stays within the WSL stack, giving a performance boost.


So the new goal now is to mount source code from a user WSL distro such as Ubuntu to that Docker Engine WSL distro. That’s where the native Linux performance comes from.

As one of the Microsoft posts I previously linked to, my take is that the performance improvement is because both the Docker Desktop WSL distro and the User WSL distro have access to the same resources within the same system space: WSL.

From Introducing the Docker Desktop WSL 2 Backend:

The bootstrapping distro also manages things like mounting the Windows 9p shares in a place that can be accessed by the Linuxkit container (to avoid using Samba for sharing Windows files with containers), and controls the lifecycle of the Linuxkit container (ensuring clean shutdown etc.).

So in my case both the Docker WSL distro and Ubuntu WSL distro are accessing Windows 9p within the WSL environment.

So as long as Docker Desktop doesn’t have to go fetching files outside of the WSL space, it will offer that alluring native performance. While both Samba and Microsoft’s 9p implementation are network-based protocols, no longer the bridge between the Windows network stack, and the Hyper V network stack need to be crossed.

Here’s a picture of the new WSL 2 architecture for reference, with the bold text to right added by me:

All the action happens inside WSL. This is a derivative work for education purposes only, original diagram may be subject to copyright by


Nothing Is Totally Free, Ever


There’s a small price to pay for this arrangement, though. Lost will be my separation of concerns.

Where before I had running Docker Compose in Ubuntu WSL, and PHP running inside Alpine WSL, I’m having a hard time seeing how to pull that off with the new WSL 2 + DD 2.2 stack. Why?

When I open and connect Visual Studio Code to a WSL distro via the Remote Explorer, that WSL distro needs to: 1) Have the PHP source code that I’m going to be editing and debugging, and; 2) The PHP interpreter, needed to debug such PHP code.

The difference now is that where with WSL version one that source code was mounted from Windows into three different places: 1) Ubuntu WSL (Docker Compose); 2) Alpine WSL (PHP Interpreter); 3) The Docker Engine (Hyper V).

With WSL 2, in order to be performant, the source code needs to be mounted directly from the users’ WSL distro directly into the Docker Desktop WSL distro. Windows is basically out of the picture with WSL 2, so to speak.

So I can choose to have the source code in Ubuntu WSL, along with Docker Compose. Or, I can have it in Alpine WSL, also with Docker Compose.

Unless I can somehow efficiently and automatically mount the local source code from Ubuntu WSL into Alpine WSL or vice versa. In which case we’d be good.

But that’s this fine afternoon’s dilemma, and why I have written this story.

All roads currently point to having a more monolithic architecture with the source code, Docker Compose, and the PHP interpreter sitting in the same WSL distribution. If that improves my Docker workload performance by more than 50%, then consider me sold.

Watch this space.


~~ Stay Dockerized My Friends ~~


8:15pm Update — Super Bowl LIV Special


Jennifer Lopez and Shakira have finished playing. Which means it’s time to weigh my options:

  • Install PHP in Ubuntu. Then use VSC Remote Explorer from there.
  • The Scott Galloway Gangster Option (Seriously, read his articles — so good). Eat my own dog food. Ditch Ubuntu, use Alpine for Remote Explorer (editing, debugging). I already have PHP installed there (Jan 28), so just install Docker Compose in Alpine WSL (Jan 2) and we gucci mane.
  • Used data volumes. This is always an option. In compose my stack I have two environment modes: development and production. My production mode uses a data volume with Drupal installed in it mounted into the stack. But VSC Remote Explorer, both when it’s working inside Docker containers and WSL distributions installs Node dependencies that are a bit overwhelming resource wide and a bit sluggish when run inside containers. The same Remote Explorer limitation applies to named volumes, which are more ephemeral in nature compared to data volumes.

I’ve been an ardent proponent of Alpine for at least 3 years now, so you might suspect where this is leading to.

In favor of Alpine:

  • It’s running the same exact Alpine distribution as my Docker workload (read: containers). Credit to Kelsey Hightower for inspiring me to use the term workload. He might chastise me for using in a non-Kubernetes context :P, though.
  • Even though WSL and container Alpine are one minor version apart, they are both using the same major, minor, and patch version of PHP (7.3.14). And the same version of PHP packages. Using the same PHP package versions across editor and container is going to be the argument to beat.

Against Alpine:

  • Alpine WSL and Docker Image Alpine versions are one minor version apart. My containers are running Alpine 3.11. WSL Alpine is on 3.10.
  • It used to be that VSC Remote Explorer didn’t work (although this documentation only mentions SSH, it used to be the case for WSL too earlier in 2019).

In favor of Ubuntu:

  • Other than Docker Compose installed on it…not much of a fight in favor of.

Against Ubuntu:

  • My Docker workload ain’t running Ubuntu.
  • The PHP version available on the stuck Ubuntu WSL (7.2.24) is one minor version behind the PHP version running in the containers (7.3.14), not to mention also patch version discrepancies. This is potentially a killer. The flag is thrown, 49ers have the ball.
  • The PHP packages on Ubuntu WSL are going to be behind the Alpine container package versions. 49ers only 10 yards away. C’mon Mahomes, rally that Ubuntu defense.
  • Alpine compiles against musl libc, while Ubuntu compiles against GNU C. Touchdown, 4th Quarter, SF 20 to KC 10. End of game, unless a redeeming miracle happens I think we know who’s taking home the trophy: Alpine.

Sorry Mahomes, I really tried rooting for you.


If I stuck with using Ubuntu WSL for editing and debugging, while running an XDebug session from Ubuntu WSL with Remote Explorer against an a codebase executing inside an Alpine container, each environment is going to be pointing to PHP packages with wildly different versions, with differing minor and patch PHP versions, and compiled against potentially different C library dependencies. And as much musl c may attempt to be interoperable with GNU C, results are not guaranteed…

Knowing for a fact that Visual Studio Code’s Remote Explorer runs perfectly fine inside Alpine WSL as of August 2019, I think all round of arguments are closed.

9:36pm: But Mahomes’ KC does touchdown, so I guess Ubuntu WSL is still in the fight for editor and debugger of choice. MVP option anybody?


9:45pm — Fourth Quarter Update, 49ers still ahead by 3 points 20–17 while using Alpine Linux, but KC’s Ubuntu is 10 yards away from the red zone…


The instructions I wrote on January 2, 2020 for installing the Docker client and Docker Compose on Ubuntu WSL will not apply to Alpine WSL, because the package names are going to be different.

9:48pm, No. 26 (Williams) scores. Goes to scoring review. Call Stands. It’s a touchdown for Williams and Kansas City. KC 23–20 49ers. Ubuntu lands the kick. Now 24 KC — 20 49ers. 2:44 minutes remaining … it’s over for San Francisco.

No matter who won the Superbowl, you my dear reader, knew the process was rigged from the start (not fair, post half-time) for Alpine Linux. And that’s a flag, running with the ball.

Let’s cook up some Alpine instructions for installing Docker Compose and the Docker client.

9:58pm, 1:33 minutes remaining, Bloody hell, that was a close Hail Mary pass from 49ers. Mahomes is safe. And so is Alpine. But seriously. 10:02pm, that -1:12 touchdown by Williams. AGAIN. Just WOW.

Right, the Alpine instructions:

# Install Docker client and Docker Compose on Alpine 3.10, WSL version.
sudo apk update
sudo apk add docker docker-compose

10:20 Final Update: Congratulations, Mahomes and Kansas City on your Super Bowl LIV win.




~~ P.S. I should really stick to engineering, I suck at Football ~~


Organic, fair-sourced DevOps and Full-Stack things. This is a BYOB Establishment — Bring Your Own hipster Beard.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store