One of the beautiful things about leveraging containers within the context of development is that containers abstract away the dependencies from the host system, and allow you to manage those dependency versions right alongside the code itself. This abstraction has many benefits, for example, it allows you to simultaneously maintain multiple versions of a particular language, package, or library.

Development Environments

If you’re using macOS, it’s likely that your host machine comes pre-installed with an old version of PHP, i.e.,: php 7.3.24; however, you likely want to work with a more recent version for your particular use-case.

Alternatively, you could be working on multiple projects, each of which require a different version of Python, e.g.,: One project uses python 3.7.1, while the other is built on python 3.8.6.

There are many ways to handle installing these different versions on a macOS workstation, like using Homebrew, but containers not only make it simple to stand up new software for development with little effort, they do it without leaving artefacts littered across your development workstation. It’s common to want to experiment with a new stack, and containers can reduce the friction of getting started.

Remote - Containers

I’ve written before about the use of destructible development environments, and the proliferation of containerized runtimes make this more and more simple every day; however, getting the environment set up, and potentially sharing that recipe with a coworker/colleague could be daunting, or time consuming.

This is where Microsoft’s Remote - Containers extension for Visual Studio Code attempts to streamline the process.

The Remote - Containers extension allows you to quickly spin up development environments for your project based on either its library of first-party (or community built) templates, or your own. It also gives VS Code (and its extensions) a direct line-of-sight into container, and manages the mounting (or copying) of your project files into the container instance.

Now that we’ve discussed what it is, how do we get started?

Installing the Extension

There are two key prerequisites for working with the Remote - Containers extension for VS Code, and they are:

  1. VS Code 😅 (of course!)
  2. Docker Desktop

Note: This article is within the context of a macOS environment. If you’re working on Linux or Windows, please refer to the documentation.

Installing the extension directly from Visual Studio Code can be done through the Extensions panel, accessible by entering ⌘-⇧-X, or by selecting Extensions: Install Extensions from the command palette (⌘-⇧-P). Alternatively, you can install the Remote: Containers extension directly from the Visual Studio Code Marketplace, by pressing the Install button directly from your browser.

If you still have the sidebar enabled, you should now see the Remote Explorer button.

VS Code Sidebar

Creating A New Environment

Now that you’ve got the extension installed, we can create a new containerized development environment directly from VS Code. Microsoft maintains a number of container definitions themselves, but there are also a large number of community-maintained definitions.

A huge part of the value prop here is the selection of curated environments that you can effortlessly begin working with, which make it simple to test out new technologies without struggling to try and determine which packages and dependencies are required to get started.

Examples of pre-built development environments include:

  • Azure Ansible
  • Azure Bicep
  • Azure Functions for Node.js
  • PHP
  • Python 3
  • Python 3 w/ Anaconda
  • R
  • Vue.js

There are even examples of development environments that leverage multiple containers, such as **Python 3 + PostgreSQL, which allows you to quickly get setup with a Python environment along with a PostgreSQL database in matter of seconds.

The full list of curated environments is available here on the project’s GitHub Repo.

For this example, let’s create a simple Python 3 workspace which could be used to work with a typical Python project.

1. Open A New Project Folder

First, create an empty project folder, and open it in VS Code (⌘-O).

2. Add the Development Container Configuration Files

Once the empty project folder is opened in VS Code, we can add the configuration files that define our development environment. From the command palette (⌘-⇧-P), select Remote-Containers: Add Development Environment Configuration Files….

Adding remote container config settings to a project

Next, you’ll be presented with a dropdown which lists some common container types. We’ll select the one labeled Python 3.

Selecting a remote container type

The next prompt will ask you to select the version of Python 3 you’d like to work with, which is one of the huge benefits to working with a containerized environment. For this article I’ll select version 3.8, but feel free to choose any Python version you’d like to work with.

Select a Python version

Finally, you’ll be asked whether you’d like to include Node.js in your development environment – I’ll omit Node from our settings.

Installing node.js within the environment

3. Review the Configuration Settings

Now that we’ve added the configuration settings, you’ll see that VS Code created a new folder within our project, .devcontainer, which contains two files: devcontainer.json, and Dockerfile.

  • devcontainer.json - This file contains your project specific configuration files. It can be used to define things such as your Python language server, formatting options, or which extensions are installed locally within the container.
  • Dockerfile - The Dockerfile contains the instructions to build the actual environment itself. As you’ll notice, the Dockerfile pulls from a pre-built image stored within the Microsoft Container Registry; therefore, this file allows you to add additional steps to the container image build process.

4. Start the Development Environment

We’re now ready to launch our development environment and open our workspace within the container itself. To do this we can select Remote Containers: Reopen in Container from the command palette (⌘-⇧-P). This will cause the VS Code window to reload, and launch the container instance.

Opening a folder in a remote container

This process automates a few steps for us, such as:

  1. Building our Docker image from the Dockerfile, which includes pulling the base image from the Microsoft Container Registry
  2. Instantiating our container instance
  3. Mapping our project folder into the container
  4. Installing our desired extensions within the container

Building the remote container image

You can watch the progress of the container image build through VS Code’s built-in terminal.

Info: The first time you start the container environment, it might take a few minutes as it's building the container image. Subsequent runs should be much quicker.

Additionally, it allows us to conveniently map ports from our container instance to our local workstation, for example mapping port 443 through to our container so that we can preview a web service.

5. Get Coding

Once the container has been instantiated, all of your project files are mounted into the running container instance. All of your loaded extensions will also be running from within the container image, therefore minimizing any of the additional steps that are typically required to make your extensions container-aware.

While you’re working within the remote container, VS Code will automatically open all of your built-in terminal windows inside the container itself, so you have direct access to your container’s console.

VS Code terminal window

For example, executing the python command within the VS Code terminal will now run whatever version of Python you selected during the configuration process.

From my local machine, executing python --verison returns Python 2.7.16, because the default Python version in macOS Catalina is Python 2.7; however, if I execute the command from within the VS Code terminal, I can see that I’m now using the container’s Python runtime, which is Python 3.8.9.

A Note About Git: If you're planning on using Git from within the remote container environment, and you use SSH keys to authenticate your repo, you'll have share your keys into the container. You can do this easily from your local machine's terminal by running the command ssh-add. For more info on the ssh-add command, see here.

6. Customise Extensions

Once our development environment is up and running, we can install any of our language-specific extensions into the development environment itself, by following the normal installation process (⌘-⇧-X). When the extensions are installed, they will be added to your devcontainer.json file, which ensures that they’re present every time you launch this environment.

This is great if you work on multiple different computers, and you’d like to version control the development environment with your project, or if you are working on a team and you want to ensure that new contributors have a set of baseline extensions installed by default.

Possibilities

As you can imagine, there are many opportunities to make use of the Remote - Containers extension within your workflow, as this post barely scratches the surface. The extension allows you to harness the power of containerized development environments, without having to be a Docker Captain from day one. Even if you are, there’s lots to love about how the curated container settings can accelerate your new projects.

Will you use it to test out a new language or framework? Or simply create a clean environment for your favourite language? There are lots of exciting possibilities with VS Code’s Remote - Containers extension.