Or, "How I will never install the JDK on my laptop again."
While setting up a dev environment and getting things just so can be rather enjoyable, it also can be a chore, especially when you're just trying to get something done. So what if I told you you could pretty much forget about setting up your project's dev dependencies?
Enter VS Code dev containers, easily one of my favorite things right now. And I want you to be as excited as I am. Now, as a disclaimer, I work for Microsoft and am going to talk about Microsoft tech, but I don't work on the team responsible for this, and my enthusiasm is genuine. Just ask all the people I work with who have had to suffer me realizing what I could do here.
Dev containers allow the following to be true:
- I can speed up setup of a new dev machine (or a reimaged one).
- I can empower anyone to immediately start working with an open source project.
- I can have have custom remote environments that I can leverage from anywhere.
Nor does it need to! Dev containers are just an option for how all the tooling used to work with your project gets made available. It doesn't impact how your code is hosted and run at the end of the day.
Containers are just a way of having a set of dependencies that can be run anywhere. That's it. What's the old joke about containers? "People kept saying 'it works on my machine,' so we just decided to ship their machine.'" Joking aside, yes, this is exactly what I want. Let me have a standard dev machine I can always have, ready to go.
That's all a dev container is. It's a little running environment that hosts all the stuff that I would start putting on a new dev box, other than an IDE. I still get to have my local VS Code in my existing OS, with all of my personalization choices. VS Code connects to that environment, and it looks just like I have all of the right tools with all of the right versions, even though I'm not installing any of those SDKs, CLIs, etc. in my host OS. I can just rely on the container.
Ok, so there are some baseline dependencies you need to play with this stuff. Yes, I realize this is a little hypocritical given the stated mission. But these are dependencies I can live with, personally. And they're kind of point in time - more on that later.
Now, let's do some development. I happen to work on Azure Functions, and we're going to use that now, but despite the name, you do not need to be an Azure customer to do any of this, nor do you have to know anything about Functions or the language you pick below. We're just going to do the basics to prove out the container, and I'll walk you through all of it.
Choose one of the below options that you are not currently set up to do. And if you're set up to do all of those already, then 1) I'd love to hear about that, and 2) just pick your favorite.
- Authoring C# Azure Functions
- Authoring Java Azure Functions
- Authoring Node.js Azure Functions
- Authoring PowerShell Azure Functions
- Authoring Python Azure Functions
Clone the repo or download the zip from your chosen link above, and then launch VS Code, opening that folder. (
code . from the terminal is how I normally go.)
Once VS Code launches, it should prompt you to reopen the project in the dev container. You can also run
CTRL+SHIFT+P -> Remote-Containers: Reopen in container.
And now we wait a bit. Our environment is getting built because all of those repos use a local Dockerfile. Things can be a little faster if we had the images hosted on a registry somewhere, but this is more flexible. The image is cached locally, so this is only going to happen the once, but the build admittedly isn't the fastest thing in the world.
VS Code will eventually launch into the remote context. You'll be able to see that the workspace is remote using a container! You also may notice that a few new extensions are in place. These are actually hosted by the container itself; if you switch to a different local project, you won't see them. Nothing has changed on your system other than a cached container image.
So let's do something in our new environment. The repo you cloned has instructions for adding a function, but it already has one included. Let's run it. Hit
F5 (or in the menu,
Run -> Start Debugging). You'll see the integrated terminal pop up if it hasn't already, and you'll see some logs pop up as the Functions runtime gets going. Notably, you'll see a list of HTTP-triggered functions, which has the one we just created.
Go ahead and
Ctrl+click the localhost link there. It'll open your browser to a page that says "Please pass a name on the query string or in the request body." Add a
?name=DevContainer to the URL and hit that to get our glorious Hello World. Feel free to set breakpoints and play around with it!
It's a simple example, sure, but you just went from zero to debugging a project in only a few steps. And you're now in a position to develop something that your system might not have been set up for before. And if you want to get rid of it, you can just remove the image, and it's like nothing was ever there.
So, this container doesn't actually have to be running on your development machine. Part of the reason dev containers are one of my favorite things is that Visual Studio Codespaces is one of my favorite things. If you happen to have access to the Visual Studio Codespaces preview, you can spin up your container there and just connect to it from any VS Code client or use the browser editor. This may or may not work with GitHub Codespaces - I haven't tried that yet.
From your browser of choice, head to your environments dashboard and select "Create Codespace" (or in VS Code use
CTRL+SHIFT+P -> Codespaces: Create New Codespace and follow prompts). Go ahead and Choose "Default settings" and then paste in a link to a GitHub repo with your container and the project that will be mounted in. Give the environment a name, and then wait for the magic to happen. You'll be able to connect to the environment and get working right away.
So remember those things I had you install to try things out? Well, going this route, we don't need Docker locally, and once you can do everything from the portal, we don't necessarily need VS Code and the Remote Container extension either. As long as I have an account, all I need is a browser to get to work on my projects. That's powerful.
If I've sold you on the idea, you may be wondering how you can make these yourself. I'll admit that I cheated a little in creating the earlier samples, but you should, too.
In a local project in VS Code, run
CTRL+SHIFT+P -> Remote-Containers: Add Development Container Configuration Files... This will prompt you with a list of containers for a variety of languages, which make a great place to start. Those are sourced from the vscode-dev-containers repo, and I certainly relied on the built-in Functions ones.
The dev container is defined by the
devcontainer.json, which can specify a local
Dockerfile that has the environment all set up. It can also point to VS Code extensions that should be loaded, but I want to call attention to the
postCreateCommand option, which is great for initially resolving code dependencies with something like an
npm install. Full reference for the file can be found here.
One thing that gives me pause is knowing that I have to make sure the the image is patched and up to date. There are solutions for this, but it's something to be mindful of in general. There's also the question of if I can trust a container from someone else. I think about this somewhat similarly to how I think about trust of any tooling, or even package management. However, the trust relationship is a little different when I'm looking at a given project versus a tool the community has rallied behind and vetted.
Regardless, I'm curious to see how the conversation evolves around these. There's also a lot to be said for having a dev container that helps bring along all the right and latest patched versions of requisite tools, too.
If you're working from VS Code on your machine, the images that back your containers are cached locally. This is great! But they can also get kind of big, and that can be less great. I find that between dev containers and stuff I'm building for services, I fill up a disk fast. So it's good to periodically get rid of images you don't need.
In your terminal, get your list of images with
docker images. Find what you want to remove and note its ID. You'll pass that into the next command, which removes the image
docker rmi <image_id>. There's a chance Docker might yell at you here because the image is being used by a stopped container. If you find yourself in that state, you can get rid of all stopped containers with
docker container prune.
My sincerest thanks to the devcontainer and Codespaces team who supported me through early previews, and major congratulations to them on putting some truly awesome tech out into the world.
I'm really excited to use dev containers wherever possible. Setting up demos? You bet. Experimenting with a new technology? Absolutely. Making things work consistently across machines I use? I see little downside.
But my enthusiasm goes beyond personal productivity. This is a way to make a codebase more accessible to newcomers, and I truly hope it becomes common practice. I think a lot of us have made peace with the idea of losing some amount of time to setting up a machine, enlisting in a new codebase etc. But we now have tool to help folks avoid that productivity hit. If you maintain an open source project, consider adding one to make things more approachable (especially once GitHub Codespaces is more widely available). If you're part of a team that needs to onboard new members, look to this as a tool to make things easier and get them going faster.
Related, I've got my eye on Cloud Native Application Bundles (CNAB), which seems to do some of the same things, enabling anyone to deploy an app. That will probably be one of the next things I play with, and I have a feeling it will combine really well with dev containers.