DevContainers: How to Standardize Development Environments with Docker
What is a DevContainer?
A DevContainer is a Docker container that pre-installs the tools and services necessary to develop a project, allowing the user to develop it from inside the container.
DevContainers are employed as a workflow for development teams operating across multiple projects, each requiring different tools, services, and versions. Thanks to them, teams can eliminate the overhead and misunderstandings that arise when starting a new project, staying under development conditions that closely mirror the deployment system.
Use cases
- Environment standardization
- Parity between the development and deployment environments
- Rapid context switching between one project and another
How do I create a DevContainer?
To create a DevContainer, you must pre-install a compatible IDE (such as Visual Studio, Visual Studio Code, IntelliJ IDEA, etc.), Docker Desktop, and the IDE extension to work with DevContainers, only if required. For this example, the Visual Studio Code IDE will be used with the Dev Containers extension.
Create the .devcontainer/devcontainer.json file inside the folder where you want to develop your project, and paste the following content:
{
"name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm"
}If you press
F1and search forDev Containers: New Dev Container...in Visual Studio Code, you can create a DevContainer using the interface.
The devcontainer.json file is used to configure the environment, tools, and execution of the DevContainer. In the example, the configuration establishes a development container with Node.js (v22) and TypeScript (v5).
Okay, now to initialize the DevContainer, start Docker Desktop, press F1, and select Dev Containers: Reopen in Container in Visual Studio Code. This will cause the development container to be built and use the project folder as a bind volume, so every action involving the creation, modification, or deletion of a file or folder will be reflected in the original project folder.
That’s all! You are now in an isolated environment with the tools you need to develop a project with Node.js (v22) and TypeScript (v5) without having to install them on your local machine.
You can use “Features” to add new tools to the DevContainer, as shown in the guide to creating an advanced DevContainer.
Guide to creating an advanced DevContainer
It is time to learn how to create a more advanced version of the DevContainer, but Why? Although for the vast majority of cases such granular configuration is not necessary, if you want to orchestrate services, install different tools, or execute scripts when building the container, we will have to opt for an advanced configuration.
Returning to the previous example, the project requirements have changed and now we want to use Bun as a package manager, a compose.yaml file to manage future services, and we wish to execute a Bash script that shows a welcome message when the container is built.
1°) To add Bun to the DevContainer, we will have to use the features property of the devcontainer.json file, as shown below:
{
"name": "Node.js & TypeScript",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/devcontainers-extra/features/bun:1": {
"version": "1.3.3"
}
}
}Features are additional functionalities added to development containers, these can range from package managers to Linters, and can even be official functionalities or created by the community.
Get Lucas Hoz’s stories in your inbox
Join Medium for free to get updates from this writer.
When rebuilding the container (pressing F1 and selecting Dev Containers: Rebuild and Reopen in Container), the Bun Feature (version 1.3.3) will be installed and available in the DevContainer.
2°) To use a compose.yaml file, whether to have granular control of the development container or to instantiate services that the project needs, we will have to modify the devcontainer.json file so that it uses the Compose configuration.
For this example, we will use the following compose.yaml and Dockerfile (it is important that both files are located inside the .devcontainer folder):
---
# compose.yml
name: my-first-devcontainer_devcontainer
services:
devcontainer:
build:
context: ./
dockerfile: Dockerfile
volumes:
- ../:/workspaces/My-First-DevContainer:cached# Dockerfile
FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm
CMD [ "sleep", "infinity" ]It is assumed that the project is located inside a folder named
My-First-DevContainer.
And as we mentioned, we will have to modify the devcontainer.json file to use the compose.yaml, so its content will be as follows:
{
"service": "devcontainer", // Nombre del servicio (definido dentro del `compose.yaml`) que se utilizara como el contenedor de desarrollo.
"dockerComposeFile": "compose.yaml",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers-extra/features/bun:1": {
"version": "1.3.3"
}
},
"remoteUser": "root" // Evita conflictos de permisos.
}If you press F1 and select Dev Containers: Rebuild and Reopen in Container, you will have built a DevContainer from a compose.yaml, so you could eventually define the services (databases, replicas, etc.) necessary for the project within it.
3°) If we want to execute Scripts when certain events occur in the DevContainer, we will have to use the properties provided by the devcontainer.json file (such as initializeCommand, onCreateCommand, postCreateCommand, etc.).
For the desired requirement (showing a welcome message when the container is built), we will use the postCreateCommand property, so the devcontainer.json file will look like this:
{
"service": "devcontainer", // Nombre del servicio (definido dentro del `compose.yaml`) que se utilizara como el contenedor de desarrollo.
"dockerComposeFile": "compose.yaml",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"features": {
"ghcr.io/devcontainers-extra/features/bun:1": {
"version": "1.3.3"
}
},
"remoteUser": "root", // Evita conflictos de permisos.
"postCreateCommand": "echo 'Welcome to the DevContainer'"
}By using this property, every time the DevContainer is built, the Script set as the value will be executed, which in this case is a welcome message.
That’s it! Having followed these steps, we configure a DevContainer that meets the new project requirements.
If you are looking for an example that is in production, visit the UNLaM Calendar project repository.
Tips and recommendations
- In
devcontainer.json, you can define which IDE extensions should be installed in the development container. - To share Git credentials (SSH keys, GPG keys, etc.) between your local machine and the DevContainer, you will have to follow the official Visual Studio Code guide for sharing credentials.
- If you use the
settingsproperty of thedevcontainer.jsonfile, you can modify the IDE configuration in the DevContainer. - Use official Features, but if that is not possible, pin the versions.







