**Keep reading macOS and Linux users, this article is for you too!**
Recently, I’ve been looking for ways to simplify and separate my programming environment from the rest of my stuff. This is partly driven by how time-consuming it can be to set up a programming environment. Whether it has been at work or at home, trying to remember all the little details just to write a line of code is not fun.
And for those of you who like to skim, here’s the TL;DR version:
This is a tutorial for how to use the VS Code Remote-Containers extension to containerize your development environment. First, I will discuss my reasons for separating my programming environment and why virtual machines didn’t work. Then I’ll show a simple example using a containerized Python development environment. Finally, I’ll give you my reasons why containerizing the development environment fits what I’m looking for in a solution.
Reasons I Separated My Programming Environment
Before we get started with the process, I want to share a few of the reasons I had for isolating my programming environment. You may share my reasons, or you may have ones of your own! Regardless, separating can be a helpful endeavor.
1. Sharing with Minimized Duplication and Maintenance.
At home, I use my desktop for both gaming and programming. My laptop, on the other hand, is more for programming than gaming.
Using two machines also adds to the pain of making sure my two programming environments are somewhat similar. If I want something new, I have to install it on two computers – yuck. One install is enough; I want something that I can easily share between computers.
2. Keeping Things Clean
Another reason for separation is the great number of different languages I’ve used. Recently, I installed Python to do some simple projects. When I’m done, I probably won’t uninstall it, so it will get stale – much like other languages I’ve installed.
3. Preventing Bloated Backups
Frankly, I don’t want to manually manage what I should include and what I should ignore in my backups. It would be nice to back up my basic Windows environment and not include my local maven repository or game installs in my system image.
4. Don’t Cramp my FPS
In all honestly, my eagerness to separate is also driven by the unfounded feeling that my programming environment is slowing down my games – ha :).
No matter what your specific reasons are, there should be a quick and easy way to set up a clean, shareable programming environment.
Finding a Way to Separate
At first, I tried to solve this problem by using virtual machines. In theory, this checks all the boxes. I could easily separate my programming environment from my host computer and create a separate environment for each programming stack. For example, with Python, I just cloned my base image and installed Python. When done, I either archived the image or deleted it.
The downsides of this method are sharing or maintaining the base image across computers, multi-monitor support, and performance. Since the virtual machine uses a generic display driver, it doesn’t handle multiple high-resolution monitors very well. When pushing the virtual machine, you could tell it wasn’t performing as well as running on the host.
Fast forward to the age of containers, Windows Subsystem for Linux (WSL), and some personal free time. Woohoo!
Since I have a Windows box, I updated my WSL to version 2, the latest VS Code, and the latest Docker. While performing the updates, I ran across a new VS Code feature called Remote Development.
According to the documentation, “Visual Studio Code Remote Development allows you to use a container, remote machine, or the Windows Subsystem for Linux (WSL) as a full-featured development environment.” WHOA!!
Remote Development Features:
- Develop on the same operating system you deploy to, or use larger or more specialized hardware.
- Sandbox your development environment to avoid impacting your local machine configuration.
- Make it easy for new contributors to get started, and keep everyone on a consistent environment.
- Use tools or runtimes not available on your local OS, or manage multiple versions of them.
- Develop your Linux-deployed applications using the Windows Subsystem for Linux.
- Access an existing development environment from multiple machines or locations.
- Debug an application running somewhere else such as a customer site or in the cloud
VS Code Remote Development looked exactly like what I was looking for. So, I decided to put off some programming to focus on this wonderful rabbit hole. In the following section, I will be sharing my process with you!
To test this out, I will be using Python as an example stack. While this “stack” won’t mimic a complex stack, it will help you wrap your head around the process and determine if it’s the right choice for your unique situation.
The Steps to Separating Using VS Code Remote Development
The Environment:
- Windows 10
- WSL2 (Ubuntu)
- Docker 2.3 with WSL2 Backend enabled
- VS Code
Step 1 – Create the Workspace
The first step is to use an Ubuntu terminal to create a new workspace and launch VS Code.
john@Azza:~/workspace/pocs$ mkdir pythonhelloworld && cd pythonhelloworld john@Azza:~/workspace/pocs/pythonhelloworld$ code .
As shown in the terminal, VS Code is doing some extra work getting the backend ready.
Step 2 – Create a File to Run
Next, create a new, wonderful Python file in the VS Code workspace. Depending on the language you use, VS Code may prompt you to install an extension related to the language. I usually install the extensions it recommends, but use your best judgment.
Helloworld.py
print("Hello World from a Container.")
Once you have created the file, you can already run the application in a WSL terminal within VS Code.
It’s nice because you can separate the programming environment from Windows. The Python dependencies are still needed for the WSL, but they won’t be accessible via Windows Powershell.
While this is handy, it doesn’t solve the duplication and maintenance problems we discussed above. I would still have to repeat this process for each computer I own.
In the snapshot below, note that the VS Code terminal and green block shows the remote connection information to your WSL. We’ll see how this changes later.
Step 3 – Create the Remote Container
This is where the magic starts to happen.
First, install the Remote – Containers VS Code extension.
Next, run the application in a remote container by using the command Remote-Containers: Reopen in Container
Command Palette (F1).
While there are a few ways we can run the application in a container, I’m going to use the quickest and easiest route: using a pre-configured template. I’m focusing more on the feasibility aspect, and since this is only a POC, I’m going to use the pre-configured template.
Similar to other project generators, VS Code will assist you by asking a series of questions about your target environment.
Once VS Code finishes doing its work, it will automatically re-open, and a dialog message appears (as shown in the snapshot below). For now, it’s okay to choose the Ignore
option.
Now, let’s look at the terminal and green block again. VS Code is telling us we are remotely connected to a running Docker container. Nice!
root@3810643b1adb:/workspaces/pythonhelloworld# python --version Python 3.8.5 root@3810643b1adb:/workspaces/pythonhelloworld#
Nice work! Python is now in the container.
Step 4 – Run the App
Alright, let’s run the Python application again.
Sweet! The Python application successfully ran in the remote Docker container! Even though the application is super simple, it’s still pretty great! I can now program in Python without installing Python manually, and I’m also not cluttering my Windows OS. Now that’s win/win if I’ve ever saw one!
So What Exactly Happened?
If you are performing these steps as you read this, you may have noticed VS Code added files to your workspace.
Again, like other generators, VS Code generates a couple of configuration files automatically under the .devcontainer
folder. I’m not going to review the files line by line, but I would like to point out a few items.
Generated devcontainer.json
:
{ "name": "Python 3", "build": { "dockerfile": "Dockerfile", "context": "..", "args": { "VARIANT": "3.8", "INSTALL_NODE": "true", "NODE_VERSION": "lts/*" } }, "settings": { "terminal.integrated.shell.linux": "/bin/bash", "python.pythonPath": "/usr/local/bin/python", "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", "python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" }, "extensions": [ "ms-python.python" ] }
The devcontainer.json
file is one of the two files automatically generated. This file tells VS Code how to create and/or set up a development Docker container. Hey, this is starting to look like a viable option to satisfy my requirements!
If you’ve been using VS Code, you probably have already installed several extensions. VS Code extensions will also need to be installed for the WSL remote connection and any remote containers. To have VS Code automatically install additional extensions to the remote container, add the extension name to the list of extensions in the devcontainer.json
file. If you have no idea what the extension name is, just right click on the extension and add to devcontainer.json
.
Generated Dockerfile
ARG VARIANT="3" FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} # [Option] Install Node.js ARG INSTALL_NODE="true" ARG NODE_VERSION="lts/*" RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
The other generated file is the familiar Dockerfile. The base dev container already contains Python. You can see how the variables allow the two generated files to work together.
According to the Remote Container Documentation, you can also use a Docker Compose file. This feels like we should be able to set up a complex development environment.
I’d say this definitely has potential!
The Wrap Up
You can end the WSL connection by either closing VS Code or by choosing File -> Close Remote Connection. Closing the remote connection will allow you to run VS Code locally. Once VS Code closes, the container will automatically stop, but the image will remain. If you have the VS Code Docker extension installed, you can take a look at the generated images.
Even though our programming environment is very lean for our remote container, we still have the full VS Code toolset at our disposal including debugging, language context assistance, and most VS Code extensions available.
This is really cool stuff. Even without using a more complex programming stack, using remote containers in VS Code feels like it will meet my requirements going forward. The flexibility offered in devcontainer.json
and the Dockerfile (or Docker Compose) solves most use-cases for a development environment.
With this new set up, my host OS stays clean, I don’t have a bunch of stale languages hanging around, my backups are lean, and my FPS stays high! Success on a personal level!
On a larger scale, not only can I share my configuration files so that both my computers are easily in sync, but this could be successfully used in a bigger development shop as well. Getting a new team member onboarded and ready to contribute becomes a lot less painful and time-consuming.
Per the documentation, another benefit is that this isn’t dependent on Windows. It will also work with macOS and Linux hosts. How about that?!
Coming Up Next…
I’m going to keep containerizing my development environment while working on my new killer app. My next blog will report on these findings.
Who knows? By the time I have it written, Windows may be full Linux!