Simplifying devcontainers With Features

Wednesday, Jan 11, 2023 3 minute read Tags: vscode

As much as I love using devcontainers for all my local development (see here) there’s often repeatable things that I want to do in them which means I go back and copy RUN steps from previous Dockerfiles that I’ve created.

Enter devcontainer Features

A few months ago a new proposal was added to the open dev container spec (the spec that supports devcontainers in vscode) for custom features.

Features are predefined scripts that you can add to your devcontainer.json file that will add something to the base image or existing Dockerfile that you are using for a devcontainer. By doing this, you simplify the base that you’re starting from and avoid a situation where you are having to add development tooling to what could be a shared image across environments.

Using Features for my blog

The repo that my blog lives in has had a devcontainer definition for nearly two years (see!), and in that time I’ve maintained a Dockerfile that uses the base image and a devcontainer.json file that describes how to use it within VS Code.

Over time I’ve added some more to the RUN command in the Dockerfile that installed more default installs, and it just kind of did it’s thing.

Today though, I decided to port it across to using Features, and you’ll find the commit here.

The primary changes in the commit are moving away from maintaining a Dockerfile myself to using a generic base image, mcr.microsoft.com/devcontainers/base:bullseye to be precise, and adding the following Features:

1
2
3
4
5
6
7
8
9
  "features": {
    "ghcr.io/devcontainers/features/github-cli:1": {},
    "ghcr.io/devcontainers/features/hugo:1": {},
    "ghcr.io/devcontainers/features/node:1": {},
    "ghcr.io/devcontainers/features/dotnet:1": {
      "version": "lts"
    },
    "ghcr.io/jlaundry/devcontainer-features/azure-functions-core-tools:1": {}
  }

Now when the container is rebuilt it’ll use the generic base image before layering on the features that I need, making it a clearer view of what has been modified in the container that I’m developing in.

Conclusion

While my blog might be a reasonably trivial place to have a complex devcontainer, I see that using Features is a really simple way to reduce the complexity of the local container setup. It would be quite possible to reuse the Dockerfile that defines your production infrastructure and then layer some Features over that, allowing it to be used for both local development and production deployments, without the risk of developer tooling leaking out.

The other added bonus is that you can define your own Features and use them within your organisations repos. Check out the docs for insights on writing your own (it can be as simple as a JSON file and bash script!).