Sharing Our Passion for Technology
& continuous learning
〈  Back to Blog

Dockerize Your Dev Environment: Part 2

shipping containers on a boat at night

The previous post highlighted the struggle in having to maintain a development environment in our cross-platform, multi-language, cloud-native, microservices-oriented world. The operations side of many organizations have leaned on Docker to provide some sanity. Let’s look at how we can use containers on the dev side as well.

The VS Code Remote Containers extension allows you to package your entire development environment into a container while still using VS Code as the editing interface.​ Do I think VS Code is the best editor…IDE…whatever…out there? No, I think there are lots of great tools and each has their strengths. However, VS Code seems to be one of the few tools that has embraced the idea of a containerized development environment.​

We’ll be looking at three projects that demonstrate the use of dev containers. I’ll admit up front that the projects themselves are not meant to demonstrate clean code. They were written purposefully to be an accessory to this dev container demonstration. Testing, dependency injection, separation of concerns, config files, and so on, are all great ideas.​

Let’s first turn our attention to the following repository:

https://github.com/brpratt/pokesprites

Inside this repo is an images directory that contains sprites of the first 150 Pokémon. Sadly, I probably couldn’t name them all anymore despite the insane number of hours I spent playing Pokémon Blue on my GameBoy Pocket as a kid.​ I’m also not eating cake for breakfast every morning either. Nine-year-old me would be very disappointed.

This repository contains a FastAPI service with a single endpoint that serves this sprite image data.​ The code is written in Python, and while we could manually install the correct Python version and set up our virtualenv, we’ll let the Remote Container extension set up the development environment for us.

Note:

The VS Code remote container documentation provides installation directions for setting up VS Code, Docker, and the Remote Container extension. This is a one-time process that needs to be completed before we can use our dev container.

Let’s set up our dev container:

  1. Fork/clone the repository.
  2. Open the repository in VS Code
  3. Click on the green “remote window” button in the bottom left corner of VS Code
  4. Select the “Reopen in Container” command that appears in the command palette.

VS Code Remote Container extension button

VS Code Remote Container extension command palette

VS Code will start building the dev container after selecting the “Reopen in Container” command. Building the container image may take a couple minutes the first time you run this command. Subsequent runs will complete much faster as VS Code will be able to reuse the existing image or stopped container.

Opening up the integrated terminal, we can see that we’re not in Kansas anymore.

VS Code Remote Container integrated terminal with Python tools

All the command line tools we need to work on this project are available, and the VS Code Python extension has already been set up. If you were to edit main.py you’ll get autocompletion and red squigglies exactly as you would if you were developing outside of a container. The repository is set up in a bind mount, meaning any filesystem changes under the project root will be visible to the host as well.

Within the integrated VS Code terminal, let’s follow the steps in the project README to run the service. Note that virtualenv is nowhere to be found. ​We don’t need to worry about messing up our host system when pulling down our Python packages in the container since these filesystem changes are isolated from the host.

Note:

It is not recommended to install Python packages with pip outside of a virtualenv on your host. Installing packages straight to the system directories can have disastrous consequences! Indeed, there may still be times when you still need to use a virtualenv in your dev container if you use any system tools that rely on a specific package version. Although, recognize how something dangerous before becomes completely benign in a containerized environment. Having to reformat and reinstall your host is a tedious process. Restarting a broken container is dead simple.

Building and configuring the dev container is controlled by the files in the .devcontainer directory. I’ll defer to the Remote Container extension documentation to explain how this works, but essentially, we’ve committed our development environment alongside our code. Anyone using the Remote Container extension will have the exact same development environment simply by cloning and opening the repo in VS Code. No extra steps in the README. No worries about interacting with the base system, and it doesn’t matter if I’m using macOS, Windows, or Linux. We’re able to do all this using a tool (Docker) that is programming language and framework agnostic.​

Let’s leave our sprite service running (don’t close the VS Code window) and look at another example. The following repository contains a .NET Core service that stores its data in Postgres:

https://github.com/brpratt/pokeapi

Since our .NET Core service relies on Postgres, wouldn’t it be nice if our development environment had a Postgres instance available? Luckily for us, the Remote Container extension allows us to use Docker Compose to run multiple containers as part of our dev environment.

Following the same steps as the previous project, go ahead and open up the project in its dev container. Make sure this is a different VS Code window than the one with the sprite service. Once again, all of the necessary tools are available, but this time we have a database instance we can connect to.

VS Code Remote Container integrated terminal with .NET Core tools

Follow the README in the repo to run the service using dotnet run. This service exposes two API endpoints:

  1. /pokemon returns a list of pokemon and their corresponding ID
  2. /pokemon/{id} returns the type information for a given pokemon.​

Similar to the sprite service, let’s leave this other service running. The following repository contains a React application that will surface the data from these two services:

https://github.com/brpratt/pokedex

Let’s clone this project and fire up the dev container (in a separate window from the other two). Before we run the application, I want to call attention to one section of the devcontainer.json config file:

"mounts": [
    "source=pokedex-node_modules,target=${containerWorkspaceFolder}/node_modules,type=volume"
]

We’re setting up a volume mount for our node_modules directory. There’s no point in having all our Node packages accessible from the host, especially if the host doesn’t have Node installed. We’re going to see significant performance improvements pulling down 1000s of Node packages in a volume mount compared to the default bind mount. Furthermore, if we ever wanted to rebuild this dev container, we wouldn’t need to redownload all our Node packages again.​

Let’s run the application from the command line with yarn install && yarn start. We can open up our browser…navigate to http://localhost:3000…et voilà! A gallery of the first 150 Pokémon and their types.

Pokémon gallery

We can flip to the other VS Code instances and see our services logging out requests that we’re making from the frontend.​

VS Code Remote Container integrated terminal with Pokémon sprite service logging

VS Code Remote Container integrated terminal with Pokémon API logging

Once we’re happy playing the application, we can close all three VS Code windows. The corresponding containers will be stopped automatically.

So where do we go from here? The remote container extension is updated every month alongside a new VS Code release. What we’ve looked at is only a small portion of its full capabilities. For an even more unorthodox approach to development, you can even connect a VS Code instance to a dev container running in Kubernetes.​

And for those of you who are fans of JetBrains, fear not! They too are working on similar functionality that they believe will go beyond what VS Code provides today.

JetBrains employee commenting on remote development support ​ In the meantime, consider giving the Remote Container extension a try. The reproducibility of Docker containers can be used for more than just application deployment. Free yourself from the burden of having to maintain your setup one machine at a time, and start to treat the computer as a tool, rather than a pet that needs feeding. If not for yourself, then for your future teammates.

〈  Back to Blog