Remote Development with VS Code and Docker
Last month’s release of VS Code (1.35) introduced an exciting set of extensions for remote development. In a nutshell, the three extensions are:
- SSH - Develop code located within a remote/virtual machine
- WSL - Develop code in a Linux environment from the comfort of a Windows machine
- Containers - Develop code with a full-featured development environment in a Docker container
This post will be a deep-dive into Remote Containers. I will introduce the problem domain, explain how remote containers work and demonstrate a use-case.
Problem Domain
Based on experience, I’ve found that setting up an environment required to develop a project can be a lengthy and tedious process, especially if you are doing it on a regular basis. Of course, the complexity differs from project-to-project based on a range of factors. But, generally speaking, the larger a project, the more potential of a complex setup process. I’ve come across projects which have taken me hours too long to set up. Not only has this caused a significant loss in time, but also heaps of frustration.
Hardships can arise from complex and outdated setup instructions. Of course, keeping documentation up-to-date is a relatively simple process, so these types of problems should (hopefully) not occur very often. However, it’s also possible that you will face complex and unexpected issues that were not mentioned in the “happy-path” instructions provided. These are the types of issues that can lead you down rabbit holes.
A typical example is when a new-joiner tries to set up a project and struggles due to a lack of contextual knowledge and no clear course of action to take. At this point, it is common to search for a quick-fix online and hope someone has encountered the same issue. Yet, this may not yield success and they may potentially add more complexity to an already complex issue. Another option is to ask a colleague for help, as they will have probably followed the same instructions to get set up. However, the problem may not be familiar to them because the setup worked a charm on their machine. This could now consume the time and effort of two engineers, which could have potentially been avoided.
And finally, once you’ve jumped over these hurdles and think you’re ready to write some code, you have to spend time in tweaking your IDE settings and installing your favourite extensions that you just can’t develop without.
What if we could eliminate these pain points with automation? By declaring an environment once, we could create a reliable way of initialising a project and its development space with no manual intervention required. This is where Remote Containers come in handy.
Remote Containers
Remote Containers utilise Docker to create a development environment for your project. They help to minimise the manual process that is usually involved in setting up a project and environment, so that you can focus on writing code. With the use of Docker containers, you can rest assured that your environment is standardised and consistent across platforms. This means less of the classic “it works on my machine” problem.
The extension allows you to declare your environment using a Dockerfile
, defined within your project. VS Code uses this Dockerfile
to build an image and run a container, which will eventually become your development environment. Along with this, a file called devcontainer.json
is required in order to use the extension. This file is used to configure launch settings for the container — we’ll talk more about this a bit later.
Let’s see how this works in practice…
Demo
For this demo, we will be initialising a React project using create-react-app. Then, we will configure it to be compatible with Remote Containers in VS Code. The objective is to automate the setup process for the project within a Docker container and be able to develop inside it seamlessly.
Prerequisites:
- Node ≥ 5.6
- npm ≥ 5.2
- VS Code ≥ 1.35
- Remote Containers extension — https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
- Docker — installed and running
To get started, let’s use create-react-app to initialise a boilerplate project.
npx create-react-app react-vscode-container
Once completed, navigate into the project and create a directory in the root called .devcontainer
. This is where the extension will look for our configuration files and recognise that the project supports Remote Containers.
$ cd react-vscode-container$ mkdir .devcontainer
Create two files in this directory: Dockerfile
and devcontainer.json
.
$ cd .devcontainer$ touch Dockerfile
$ touch devcontainer.json
At this point, we can open our project in VS Code and start to populate these new files.
Your project structure should now look like this:
react-vscode-container
.devcontainer
Dockerfile
devcontainer.json
src
public
README.md
...
...
Firstly, we will edit Dockerfile
to be the following:
Above, we define a container extending the standard Node 10 base image. Then, we configure zsh as our default shell and install ESLint globally.
Secondly, edit devcontainer.json
to be the following:
As mentioned earlier, this file configures launch settings for the container. There are a few properties being set here, so lets go through them one-by-one.
name
: Friendly display name for the dev container.
Dockerfile
: Path to the Dockerfile
which will be used to create the dev container.
appPort
: Ports to expose from container to host machine. This is useful to be able to access your app from your local machine when it is running in the dev container. In this example, we expose port 3000 as this is where our app runs in the container.
extensions
: Extensions to install for VS Code in the dev container. This means you won’t have to install extensions manually, they will be ready to use from initial launch. In this example, we are installing: Bracket Pair Colorizer, React Code Snippets and Sort Lines.
settings
: Attributes for the container VS Code settings.json
file. In this example, we set the terminal shell to be zsh.
postCreateCommand
: Command(s) to run after the container has been created. In this example, we run yarn
after creation to install our Node dependencies.
The configurable options provide freedom over your development environment so that you can tailor it to the needs of your project and team. A list of all configurable properties are documented here.
Now, we are ready to launch our development container:
CMD/CTRL + Shift + P
> Select “Remote-Containers: Reopen Folder in Container”
A new VS Code window will open to run your container. This will take a few minutes on the first launch as an image will be built for the container.
Once completed, you will be able to see your project in VS Code. The difference being that this is running inside a development container, not your local machine. To verify this, open another terminal and run the following command which will echo the list of files in the root directory.
$ ls ../../ # bin boot dev etc home lib ....
Notice how zsh is our default shell in the VS Code terminal, as declared earlier. Also, the extensions we listed in devcontainer.json
are installed and ready to use.
You might be wondering how the project folder has magically appeared in the file browser on the left pane. That’s a fair question to ask. Prior to running the container, the extension injects arguments into the Docker launch command. By default, a volume is created for the project directory between the host machine and the container. This ensures that any changes in the container are mirrored on the host machine and vice-versa in order to achieve consistency of states in both environments.
Finally, we can run our app from inside the development container.
$ yarn start
Our app will run within the container on port 3000. Since we specified this port in appPorts
, it will be published and we will be able to access it on the host machine via localhost.
If you make a change to App.js
, you will see that hot-reloading still works and the browser will auto-reload once a change is detected.
That’s it! We’ve set up our development environment for this project using Remote Containers.
Github repo for this demo can be found here.
Summary
To summarise, we have learnt how to create a containerised development environment using VS Code and Docker. This provides us with a consistent and isolated space to build apps without worrying about setup.
A clear use-case of this feature is simplifying on-boarding processes, so that new-joiners are able to get up and running without facing platform-specific issues. For open-source projects, first-time contributors will require less guidance and face fewer issues related to setup. This will allow contributors to focus on development, rather than development environment. It also means that all users will be developing code in a standardised environment. These are just some of the potential use-cases. The possibilities are endless!
A key benefit of using Remote Containers is that they don’t come with a steep learning curve, meaning it is approachable for those who don’t have experience in dev-ops. You don’t need to be highly experienced with Docker to be able to take advantage of this feature. In fact, all you need to do is make sure Docker is installed and running on your machine. If you have done this and your project is appropriately configured, you are just a few clicks away from launching your new development environment.
Have fun and let me know your thoughts!