Getting started with VS Code Remote Containers on Windows

I've been trying out VS Code Remote Containers recently and I'm honestly really impressed with the experience so far. After some (thankfully really well documented) initial setup I am able to use remote containers to quickly setup a development environment that is bootstrapped with the tools, SDKs and runtimes required to work with a variety of different technology stacks, and without having to install and configure any of those things directly on my local machine.

There's alot of potential here and the crossover with GitHub Codespaces surely guarantees this way of working will become mainstream in the not too distant future. I thought I would share my motivations for wanting to try remote containers, and describe how I got up and running. I'm also planning on turning this into a mini series of posts to cover related topics such as personalising your remote containers and using GitHub Codespaces.

Motivation

I have always done my development work on a Windows machine. Historically the bulk of the development projects I have worked on involved Microsoft's .NET Framework so it was the only real choice. Sometimes my development environment was on a Windows virtual machine, and I ocassionally hosted a Windows VM on OSX, but for the longest time I have just installed my development tools directly on to my Windows machine.

I don't see that changing any time soon - I like Windows and I still develop on projects at work that use the .NET Framework and installing development tools directly on the host OS continues to be the simplest setup (for me) in that case - but as I have started to branch out a little into working on largely JavaScript and Node based projects and started to work more with Open Source projects (both as a contributor and maintainer) I started to wonder what tools were out there that would allow me to:

  • make my development environment more portable so it would be easier for people to contribute to my projects; and

  • allow me to more easily configure my development environment to meet particular requirements when switching to one project or another

This is of course not a new problem, and I'm aware of and tried out tools in the past such as Vagrant and Docker (and of course plain old virtual machines), but I've always eventually given up and felt limited by support for Windows and .NET Framework, and I never really did get far enough in my exploration to try out a development workflow using these tools on a "real world" project.

But, as is the way with technology, things have progressed a lot in the past few years so I thought I would do a little bit of research as to what new or improved options there might be in this space - I had read that WSL2 support in Docker Desktop improved the Docker experience on Windows and also that VS Code had some good extensions for remote development via WSL (Windows Subsystem for Linux) - so I thought I would look into WSL and maybe give Docker another go.

Heading a little bit further down the rabbit hole I then read about VS Code's Remote - Containers extension, which:

  1. sounded exactly like what I was looking for - creating and using sandboxed development containers via Docker

  2. looked relatively straight-forward and no risk to at least try out

  3. uses the same foundations and shares a lot of the tooling and configuration behind GitHub Codespaces - so (in my opinion) is likely to become a more mainstream approach than tools such as Vagrant

I realised that the Remote Containers extension and GitHub Codespaces are also not new (though Codespaces is still in early access at time of writing), and I was a little late to the party, but I also figured that that might go in my favour as I've maybe skipped over some initial pain points that early adopters had.

Having not used WSL before the first step for me was to get that installed.

Although I am a Windows user and the post is written from my experience of setting up remote containers on Windows, the experience should be similar if you're getting started with remote containers on macOS or Linux - you will need Docker, a terminal and VS Code with the "Remote - Containers" extension. See the official docs for the exact steps.

Installing WSL2

I found a few guides describing how to do this, but the one I followed was this one from the official WSL documentation. At the time (this may have changed depending on when you are reading this) I could not follow the "Simplified install" instructions because I was not on the Windows Insiders Program and didn't want to sign up to that right then. I followed the "Manual install" instructions instead.

The last step in the installation instructions is to install the Linux distribution of your choice. I am a Linux noob so picked Ubuntu because that is the default for the "Simplified install" and I thought I would just go with the default in the absence of any informed opinion of my own!

I have installed WSL2 on a couple of machines now and the first time went fine with no issues, but the second time I hit a couple of issues when launching Ubuntu for the first time. These were the error messages I got:

  • Installation failed with error 0x80370102

    • This is mentioned in the troubleshooting section and I needed to enable CPU virtualisation in the BIOS settings

    • In my case that option was under the "CPU configuration" settings and called "SVM Mode"

  • WslRegisterDistribution failed with error: 0xc03a001a

    • This appeared after the above issue was resolved and whilst it is not mentioned in the troubleshooting a quick search took me to this GitHub issue comment, which provided me with an explanation and the fix

    • My computer is configured to use NTFS compression and I had to disable it on the folder where the Ubuntu distro is installed

After resolving both of those I could carry on and finish the installation.

Now I had WSL2 and a Linux distro installed I could move on to the next step.

I'm going to stop typing "WSL2" now and just use "WSL" instead - so from this point on WSL == WSL2!

Windows Terminal is your friend

You will need to use a terminal / console app to interface with your WSL distro via a shell. I already was using Windows Terminal and the nice thing is that Windows Terminal has a feature called Dynamic profiles so it was already "aware" of my Ubuntu WSL distro when I next launched the app - I could immediately use it to open the default Ubuntu shell with no configuration required.

I did end up making one minor adjustment to the dynamic profile settings though, but that was because my default "starting directory" was set to a Windows path on a local disk. For my Ubuntu profile I wanted to set the starting directory to a path within my user's home directory, but there is a little bit of a trick to this as the startingDirectory setting in Windows Terminal only accepts a Windows-style path. Thankfully good documentation came to the rescue again and it turned out that I just had to use the WSL UNC path to the home directory, which in my case looks like //wsl$/Ubuntu-20.04/home/meeg.

Windows Terminal is of course just one option of many terminal / console applications on Windows, but if you don't currently have a preference or don't use it and are open to trying it then I would personally recommend it. If you prefer to use something other than Windows Terminal then that's cool too - you will just need to configure it appropriately to access your WSL distro's shell.

Installing Docker Desktop

The development containers are Docker containers so I needed to install Docker Desktop for Windows. This went exactly as described in the documentation for me so I've got nothing more to say here!

VS Code's Remote - WSL extension

The Remote - Containers extension is just one of a few VS Code extensions in the "Remote" stable. There is also a Remote - WSL extension that I found I needed to install to act as a "stepping stone" to working with development containers. The reason being is that:

  • Windows container images are not currently supported by the Remote - Containers extension so I have to use Linux images

  • the development containers will bind mount the local file system into the container

  • the Docker Best Practices mention that to get the best performance when bind-mounting files into Linux containers you should store your files in the Linux file system rather than the Windows file system

  • (from what I found) you can't launch VS Code directly into the development container - you open your workspace locally and then you re-open VS Code attached to the container

  • because of the above Docker Best Practices "open your workspace locally" means opening the workspace in the Linux file system

  • "opening the workspace in the Linux file system" is easiest using the Remote - WSL extension

So I installed the extension, opened up my Ubuntu (WSL) shell in Windows Terminal, created a new directory to house my workspace, and ran code . - I was now looking at VS Code running locally on Windows, but connected to the Ubuntu file system using the Remote - WSL extension.

I was now ready to try out remote containers.

VS Code's Remote - Container extension

First things first is that I need to install the Remote - Containers VS Code extension.

With that done I get a few new commands in my command pallette and the first one I want to run is Remote-Containers: Add Development Container Configuration Files, which is the quickest way to setup a new development container.

When you run this command you will then be asked to select a container configuration definition, and you can select from options such as:

  • Language / platform specific such as C#, Go, Java, Node.js, Rust, Ruby etc, which are environments with the SDKs, frameworks, apps etc pre-installed and pre-configured for you to quickly start writing programs for those languages / platforms

  • "Empty" environments based on specific Linux distros such as Alpine, Debian, Ubuntu if you want a basically blank slate from which to configure your own environment

  • The full list of all available container configuration definitions, which includes all of the "official" configurations provided by Microsoft (including and in addition to those in the initial list), plus "community" contributions like Elm, Hugo, Julia and Swift

To try this out I picked the "C# (.NET)" container configuration and I was prompted with a few more options to choose my target .NET Version (I chose .NET 5 as I do not have that installed locally), and whether I also wanted to install Node.js and/or Azure CLI (I chose to install both just out of interest), and after making those choices my configuration was generated and placed in a .devcontainer folder in the root of my workspace.

VS Code then detected that I had this .devcontainer folder and prompted me to "Reopen in Container", which I did. This built and ran the container image and reloaded my VS Code instance attached to the running container - so now I was looking at VS Code running locally on Windows, but connected to the Docker dev container using the Remote - Containers extension.

I then wanted to get a basic .NET app created to test it out so I:

  • Started a new terminal session in VS Code and ran dotnet new webapp -o WebApp to create a basic Razor Pages web app

  • Opened up WebApp/Startup.cs so that the C# VS Code extension kicked in and started installing Omnisharp and the .NET Core debugger etc

  • Opened the command pallette and ran .NET: Generate Assets for Build and Debug to generate VS Code build and debug tasks

    • The C# extension may prompt you to create these anyway

  • Hit F5 to build and launch the app in my default browser

  • Viewed the web app running on the dev container via https://localhost:5001

    • You will get an untrusted certificate warning, which I just accepted at this point, but will look to fix

So there I can see that I have a basic .NET 5 development environment up and running through the Remote - Containers extension without having to install the required SDK locally.

Conclusion

Getting the basic tooling installed to support Remote Containers was fairly painless and where I did come across issues they were quickly solved from reading through the docs or a quick search online. It's a little bit awkward having WSL as a "stepping stone" on Windows, but I can't see that being much of a problem, if at all. I'm looking forward to trying out GitHub Codespaces where there should be even less tooling involved (maybe even no tooling if developing via the browser), and this is obviously not something you would encounter with Linux or macOS as your host - though as mentioned I haven't tried on anything except Windows so potentially there are peculiarities there also. Overall though the basic setup went well for me.

Once the initial setup is done the processing of selecting and spinning up a new dev container is super easy, and developing on it via VS Code is no different to developing locally. Granted I haven't yet done a lot of development with dev containers yet (I have done more than the example shown above though!) and I haven't worked on any large projects in a dev container so I am going to have to see how it goes, but I have not encountered any issues so far.

The only downsides I can see currently are that:

  • this is of course all going through VS Code so if you are used to using other tools instead of or alongside VS Code then this won't be for you unless you are prepared to compromise or look for workarounds - one example I have personally is that I like to use GitKraken, but switched to using the Git tools and terminal in VS Code when using the dev container

  • if disk space is at a premium then the WSL distro(s) and dev container image(s) are most definitely not going to help with that - I believe/hope there is a way to move the default installation paths for these though I have not had to look into it myself (yet)

The upsides are less obvious at this point because it's just me working on my project alone. Personally I'm happy I have gone through this process to learn a bit about WSL, Docker and Remote Containers generally and it's "cool" that I can spin up these different dev environments quickly now, but as one person working on one thing it's not a game changer.

However, I can see the potential here when working with a team of people (at work, in Open Source, or just for fun) that I now have a development environment defined alongside my code that other people can use to quickly spin up their own development environment that contains all of the required software and dependencies needed to work on that project.

Of course there are still some barriers to entry here - you need at least the basic tooling installed (and a machine that is capable of running the tooling!), plus you need to accept the downsides mentioned - but I can definitely see the benefits. And if the tooling required for Remote Containers is easier to setup than the full tooling required for the development environment then it actually lowers the barrier to entry and I can see this being appealing to Open Source work or to people working in development agencies where the tooling can change from project to project and cause versioning or configuration issues.

I'm going to keep using Remote Containers for a while in my personal projects and see how it plays out, and to make myself a bit more comfortable in this new environment I have done some work to configure and personalise my dev containers with my preferred Git config, VS Code extensions, and shell configuration (prompts, plugins, aliases) etc. I will cover the configuration and personalisation aspects of dev containers in my next article in this series.