Thursday, March 2, 2023

Fast-loading Python Dev Containers

Since discovering Dev Containers and Github Codespaces a few months ago, I've become a wee bit of a addict. I love having an environment customized for each project, complete with all the tools and extensions, and with no concern for polluting the global namespace of my computer. However, there is one big drawback to loading a project in a Dev Container, whether locally in VS Code or online in Codespaces: it's slow! If the container isn't already created, it has to be built, and building a Docker container takes time.

So I want my Dev Containers to open fast, and pretty much all of mine are in Python. There are two ways I've used to build Python Dev Containers, and I now realize one is much faster than the other. Let's compare!

Python base image

Perhaps the most obvious approach is to use a Python-dedicated image, like Microsoft's Python devcontainer image. That just requires a Dockerfile like this one:

FROM --platform=amd64

Universal container + python feature

However, there's another option: use a Python-less base image, like Microsoft's base devcontainer image and then add the "python" Feature on top. Features are a way to add common toolchains to a Dev Container, like for Python, Node.JS, Git, etc.

For this option, we start with the Dockerfile like this:

FROM --platform=amd64

And then, inside devcontainer.json, we add a "features" key and nest this feature definition inside:

"": {
    "version": "3.11"

Off to the races!

Which one of those options is better in terms of build time? You might already have a guess, but I wanted to get some empirical data behind my Dev Container decisions, so I compared the same project with the two options. For each run, I asked VS Code to build the container without a cache, and I recorded the resulting size and time from start to end of build. The results:

python image base + python feature
Base image size 1.29 GB 790.8 MB
Dev Container size 1.29 GB 1.62 GB
Time to build 12282 ms 94643 ms

The python image starts off larger than the base image (1.29 GB vs 790 MB), but after the build, the base+python container is larger at 1.62 GB. There's a huge difference in the time to build, with the python container building in 12,282ms while the base+python container requiring 8x as much time. So, features seem to take a lot of time to build. I'm not a Docker/Dev Container expert, so I won't attempt to explain why, but for now, I'll avoid features when possible. Perhaps the Dev Container team has plans for speeding up features in the future, so I'll keep my eyes peeled for updates on that front.

If you've been experimenting with Python Dev Containers as well and have learnings to share, let me know!

No comments: