Getting Started With Dev Containers
Introduction
Ok, so we’ve all been there before, we join a new team, we clone the source code repository, we look at the README.md… and then we see the huge list of prerequisites required just to compile the code and instantly we want to start crying…
Or maybe we’ve been developing an application for a few months and then… oh crap our dev machine dies and has to be rebuilt! Now we have to install all our SDK’s again, install our beefy IDE again and then fight to remember all those little niggling issues that we had to hack around to get our code building on our system. I tell you… it’s enough to make any sane person scream!
But what if I was to tell you there was an easy solution to this pain?
What would you say if I told you that you can embed your development environment into your code repository?
What would you give to have everyone on your team using a consistent and reliable environment?
As with my other blog posts, I’m going to try and walk you through a recent experience I had using dev containers on a project. If you don’t have a lot of free time then please scan down to the TL;DR summary section.
Containers to the rescue
Ok so, containerisation is nothing new; in fact am sure most of you already use containers for your deployments and to run your applications in your production environments. But for those of you new to containers they provide us with the following benefits:
- Lower overhead: Containers use less resources than traditional virtual machine environments.
- Portability: Applications running in containers are abstracted away from the physical platform they are hosted on.
- Consistency: Teams can be confident that their containerized code will run the same, regardless of where they are deployed.
- Improved DevOps: Applications running in containers can easily be scaled up and down, restarted and patched due to their virtual nature.
So here’s an idea; why don’t we leverage those benefit containers give us, and use them to provide consistency to our dev environment?
So What Are Dev Containers?
Simply put, dev containers are a containerised development environment, and while dev containers can seem complicated, in practice they are actually very simple.
Back in March 2019 code-server had its first release, a simple yet powerful tool that allows you to run an instance of VS Code remotely on a server, and then connect it to your VS Code instance on your workstation via different protocols such as SSH.
Simple enough right? Well if we can connect over a network, why can’t we simply connect to an instance of VS Code Server running on our local machine running inside a container thats running on our machine? We share our network, and volume mount our source code, and then away we go! For those of you that prefer visual aids, the following image explains how the dev container works in principal:
Sounds Great — How Do I Get Started?
Now this is the best bit; when I said dev containers remove the need for lengthy setup on our development environment, I wasn’t lying. But we do still need a few tools installed to get us up and running:
- VS Code
- Docker Desktop for Windows/Mac or Docker CC/CE for Linux
- VS Code Remote Development Extension Pack
And really that’s it, you are now all ready to get started with your first dev container; give yourself a round of applause!
Hello Dev Container
Right, so you’ve installed the prerequisites, let’s go ahead and create our first dev container. You can follow along at home, or if you prefer, you can clone my handy repo.
So here are the basic steps to follow:
- Open VSCode
- Create a new folder on your machine called HelloDevcontainer
- In VSCode click File > Open and navigate to the HelloDevcontainer folder you created
- Open the Command Palette (F1)
- Type in the palette: Remote-Containers: Add Development Container Configuration Files
- Choose a template for the container. For this example let’s choose .NET Core 3.1
And that, as they say readers, is it. You now have a dev container that is ready for building NetCore 3.1 applications. But before we start hacking, let’s spend a few minutes to dig a little deeper and make some sense of things.
First let’s take a look at the Explorer pane. You’ll notice that your project folder now has a folder called “.devcontainer” which contains two files:
- Dockerfile: This is a standard docker container spec. This file tells docker how to build the container for you, basically all the bits and pieces that need to be installed in your virtual dev environment. It is automatically scaffolded out to download and install all the SDK’s and tooling that are needed to have a C# NetCore 3.1 environment.
- devcontainer.json: Think of this as the settings file that will configure the VSCode server running in the dev container. For a more in-depth reference of what is possible with this file please view the guide here
Developing In A Container
With your dev container configuration created, you can now reopen your project folder in the container. To do this:
- Open the Command Palette (F1)
- Choose the “Remote-Containers: Open Folder in Container” option.
Your IDE will attempt to build the dev container image, mount the source code, spin up the container on your local Docker demon, drop in a VSCode server and then use SSH to connect to the VSCode server running in the container.
Now the reason I chose NetCore 3.1 as the template for this examplecontainer is because I’m guessing most of you out there in reader land won’t have Visual Studio 2018 installed and/or all the NetCore SDKs usually associated with C#; but now you don’t need them!
To demonstrate what I mean, try the following in your dev container:
- Open a new bash terminal
- Type
dotnet
and hitEnter
- Notice that your Terminal window tells you the version of
dotnet
installed. - Type
dotnet new console
- The DotNet SDK now will scaffold you out a new simple C# HelloWorld console application.
The point I’m trying to make here friends is that if you push this code to a remote repo, anybody that clones it can spin up this dev container and have everything they need to extend, compile, deploy and debug your code.
The Advantages of Dev Containers
So what are the advantages of dev containers? Well they’re very similar to those of using containers in general:
- “Works on my machine”: The old analogy is never truer. Dev containers finally level the playing field, regardless of what host OS we are using, we get a consistent environment with everything we need included.
- Consistency: Everyone working on the codebase is now using the same toolset. Gone are the days of developer A using a code linter and developer B not using a code linter because they can’t be bothered to download the tooling.
- Reusability: We can easy reuse this dev container for other C# projects we build. This keeps our codebases even more consistent.
- Ease of use: Imagine someone new joins your dev team, now rather than them having to spend half the day setting up their machine, they can be up and running in 15–30 mins and productive.
- Hosted dev containers: While I haven’t actually tried this, I can imagine it would be very simple to have your dev container running in a hosted environment such as Azure Container Instances, this way you can persist your codebase, even when you turn off your laptop/desktop dev machine.
- Git works in there: So the guys that developed this VSCode extension have done some awesome wizardry to get your git credentials to work inside the dev container. This means all your credentials and ssh keys all get passed through and work automagically!
Disadvantages of Dev Containers
So, as you can already tell, I think dev containers are amazing; but unfortunately they are not without their issues:
- They take time to set up: They do have some initial upfront time/effort cost. The templates provided are amazing starting points, but you will eventually need to extend them to include extra tooling etc.
- They do add a layer of complexity: Inherently they do require some knowledge of Docker and containers, and therefore aren’t quite as simple as just opening an IDE like Visual Studio, PyCharm or Rider.
- Everyone in your team needs to buy into them: Dev containers only really work when everyone in your team uses them. There is very little value-add if only some of your team use them and others decide they are going to ignore them and do their own thing.
- They can become stale: Because of the fact the dev container is not part of the main codebase, it is very easy to forget to keep the dependencies updated.
- No support for Windows images (yet): So apparently it’s coming soon, but at the time of writing… no such joy. Time to get familiar with Linux and
Bash
peeps 😃 - VSCode LiveShare has issues: My team loves LiveShare, and rightly so, it's a fantastic collaboration tool and makes pair programming so natural. But, because of the way VSCode is running within the container, there are a few workarounds for the issues, however, some functionality such as the voice chat, may or may not function 100%.
Dev Container Top Tips
Having used dev containers “in anger” for a while now, here are my top tips for a pain free experience:
- Pin your dependency versions: Seems logical, but this one definitely burnt us on our project. The templates provided use
apt-get
commands to install dependencies and docker will cache your built containers. However, if you rebuild your container, you could end up in a situation where you accidently install a newer version of your dependency. This could cause unexpected behaviour and/or bring in breaking changes unexpectedly. - Make sure you keep your repo documentation up to date: Call out the usage of the dev container to people visiting to your repo. You need to make sure people know how to use it, and make sure you document the tooling choices and dependencies within your dev container. Trust me this will help others maintain it moving forward.
- Keep your containers simple: It can be very tempting to throw everything and the kitchen sink into your container and have 100+ dependencies installed in your dockerfile. Please please please don’t do it. Keep it simple, keep it light and save yourself a lot of complexity issues.
- Save your work regularly: From personal experience, I’ve had a few issues in the past with Docker on my MacBook going crazy and taking up 100% of my CPU or crashing randomly. While the code you are editing is the same code on your machine (because of the volume mount), if Docker suddenly dies, any unsaved changes will go with it.
TL;DR; Summary
I know you’re all busy people, there’s code to be written (and likely many meetings to be attended). So if you are busy here are some very simple takeaways:
- Dev containers are essentially a VSCode Server running in a docker container.
- Composed of 2 files; a
dockerfile
that contains the container setup code, and adevcontainer.json
that configures the VSCode server instance that runs with the container. - The
devcontainer.json
allows you to configure things such as volume mounts and VSCode extensions. See the reference guide for more info. - They ensure we have a consistent development experience/environment.
- They remove the pain of setting up our machines with all the necessary SDKs and or dev tools required to build our code.
- Dev container configuration lives within our source code repository making life easier for contributors who clone our code.
- Allow easy onboarding of new team members.
- Can be reused across multiple projects to keep things consistent.
- Currently only work with Linux container images (sorry Windows fans).
- You do need to have some existing knowledge of docker containers to get the most out of them.
- Lots of ready made example templates available to get you started quickly.
- They only work with VSCode, so if you don’t like VSCode as and IDE, then you are going to probably need to look at an alternative option (unless another IDE offers this functionality — happy to be proven wrong 😊).
Closing Remarks
Dev containers give us the perfect balance of flexibility and consistency when working with codebases. They ensure that everyone working on our codebase uses the same versions of language SDKs, the same tools and even removes the complexity of the OS they are using for development.
A few years ago I worked on a project where some of the developers in my team were using macOS and some were using Windows. We constantly ran into problems where build scripts would work on one developers machine, but then wouldn’t work on another machine. We would constantly run into issues with some developer preferring IDE specific tooling that caused problems on build servers… it was so painful!
If someone told me 5 years ago that I would be developing C# code in an IDE other than Visual Studio on a MacBook I’d have laughed in their faces. I was a Windows fanboy through and through and couldn’t contemplate developing without all my Visual Studio extensions turned on. But now… all that is irrelevant to me. With dev containers I can easily jump between Python, Go, C# and any other language out there. I don’t have to worry that my Java code won’t build because I’ve installed SDK version 1.8 rather than 1.6 🤦♂️
Dev containers finally enables the dream of cloning a codebase, opening an editor and getting stuck into developing. I can go from zero to productive in a matter of minutes just by reading the repo README file, and while they may not be perfect, the benefits definitely outweigh the issues. So do yourself a favour people, get your team started with dev containers today and level the playing field.
Further Reading
- Developing Inside A Container — A how to guide from the people that built the technology
- VSCode Dev Containers Github Repo — The repo containing all the templates provided out of the box by VSCode