Development containers in VS Code: a quick start guide

Ben Cook • Posted 2021-06-29 • Last updated 2022-09-07

If you’re building production ML systems, dev containers are the killer feature of VS Code. Dev containers give you full VS Code functionality inside a Docker container. This lets you unify your dev and production environments if production is a Docker container. But even if you’re not targeting a Docker deployment, running your code in a container prevents you from fighting with local Python environments across development machines: if you configure your dev container correctly, it will work exactly the same for every member of your team.

In this post, I’ll give you a quick look at how development containers work in VS Code so you can try them for yourself. There are other ways to go about this, but for a basic workflow, I recommend a few things:

  • A Dockerfile in the root folder to define the development container
  • A configuration file at .devcontainer/devcontainer.json
  • Whatever project files you need

This means, for a new project, the folder structure will look something like the following:

.
├── .devcontainer
│   └── devcontainer.json
├── Dockerfile
├── LICENSE
├── README.md
├── main.py
├── requirements-dev.txt
└── requirements.txt

The Dockerfile can be very flexible. Typically you’ll want to install dependencies unless your Docker image already has them installed. Here’s a very basic one you can use to start:

FROM python:3.9

COPY requirements.txt .
RUN pip install -r requirements.txt

The devcontainer.json file is also highly customizable, but a few things are particularly important:

  • The build definition
  • Workspace settings for VS Code (things like the path to the Python interpreter, what linter to use, etc)
  • Extensions to use
  • Additional commands to run inside the container

Here’s what a basic devcontainer.json looks like:

{
  "name": "Python 3",
  "build": {
    "dockerfile": "../Dockerfile",
  },
  // Set *default* container specific settings.json values on container create.
  "settings": {
    "python.pythonPath": "/usr/local/bin/python",
    "python.languageServer": "Pylance",
    "python.linting.enabled": true,
    "python.formatting.blackPath": "/usr/local/bin/black",
    "python.formatting.provider": "black",
    "editor.formatOnSave": true,
  },
  // Add the IDs of extensions you want installed when the container is created.
  "extensions": [
    "ms-python.python",
    "ms-python.vscode-pylance"
  ],
  "postCreateCommand": "pip install -r requirements-dev.txt"
}

Once you have these files setup in your VS Code session, you can open the project in a container. Open the Command Palette with F1 and run Remote-Containers: Open Folder in Container... From there, you can open a terminal and run your code.

Notice if you modify print("hello world") to print('hello world') (with a single quotation) Black will automatically change it for you on save. Nifty!

One other note: the community seems to be switching to Poetry for dependency management in Python and I think this is a good trend. Version conflicts are a huge pain in the regular pip toolchain. I will save that for another post, but just know that you can swap out the pip requirements files for Poetry.