Turn Docker compose files and services into Devcontainers
Devcontainers based on Docker compose files gives developers the choice to opt-in, or to keep using CLI tools.
Development containers make it possible to use a container as a full-featured development environment. For some, this is a preferred approach and a no-hassle way to get started. For others, it’s an annoyance.
Be that as it may, VSCode with the Dev Containers extension is a popular way to go about. Also GitHub Codespaces serves a polished experience right in the browser. They both provide an easy way for developers to get started.
It’s important to provide developers a golden path to set things up, but it’s equally important to cater for flexibility.
We have found Docker compose files to be a good middle ground.
By basing devcontainers on docker-compose.yaml
, developers can choose to fall back to Docker CLI for running containers, would they want to do so.
Solution
Our goal is to turn each service in a docker-compose.yaml
file into a separate devcontainer.
We have the following project structure for demonstration:
./
├── .devcontainer/
│ ├── node/
│ │ └── devcontainer.json
│ ├── python/
│ │ └── devcontainer.json
│ └── basic/
│ └── devcontainer.json
│
└── images/
│ ├── node.Dockerfile
│ └── python.Dockerfile
│
├── docker-compose.yaml
└── README.md
Notice, that it’s possible to have multiple devcontainer.json
files within a .devcontainer/
directory according to the spec1.
In this example, we want to provide a few different environments to choose from.
The contents of docker-compose.yaml
are:
services:
node:
build:
context: images/
dockerfile: node.Dockerfile
tty: true
volumes:
- .:/workspace:cached
command: bash
working_dir: /workspace
python:
build:
context: images/
dockerfile: python.Dockerfile
tty: true
volumes:
- .:/workspace:cached
command: bash
working_dir: /workspace
basic:
image: mcr.microsoft.com/devcontainers/base:debian
tty: true
volumes:
- .:/workspace:cached
working_dir: /workspace
In addition to a Node and a Python services, we have a basic:
service to demonstrate, how the Devcontainer base images can be used.
The contents of .devcontanes/node/devcontainer.json
are:
// .devcontainer/node/devcontainer.json
{
"name": "node",
"dockerComposeFile": "../../docker-compose.yaml",
"service": "node",
"runServices": ["node"],
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
"overrideCommand": true,
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {}
}
}
Since we are using custom containers, we’ll set overrideCommand
to true.
This is to make sure that the container won’t imediately exit.
Otherwise you may run into Error: An error occurred setting up the container
.
Also note that the volume mount in the Docker compose file matches the workspaceFolder
.
The contents for the other devcontainer.json
files follow the same pattern.
The image in images/node.Dockerfile
is defined as:
# images/node.Dockerfile
FROM node:23-bookworm
# install common tools
RUN apt-get update && apt-get -qq update && apt-get install -qq -y \
curl \
shellcheck \
&& apt-get autoremove -y && apt-get clean
ENTRYPOINT ["bash", "-c"]
Tools can be added either as layers in the Dockerfile, or using the features
config2 in the devcontainer.json
.
For example, to add shellcheck
, we could add the "ghcr.io/lukewiwa/features/shellcheck:0": {},
feature, or install it in the Dockerfile.
Given that we want to keep the Docker compose files usable as stand-alone, we should install tools using the latter approach. An exception to his is tools and plugins related to the IDE.
To start a devcontainer in VSCode, run Dev Containers: Reopen in Container
from the command palette.
VSCode will then connect to a running instance of the selected container.
Alternatively, you can start a codespace from Visual Studio Code for the Web.
To do the same without devcontainers, run the specified service using interactive mode with the Docker CLI:
docker compose run --rm -it node
Pitfalls
If you need to run Docker within the devcontainer, prefer a Docker-outside-Docker setup. You can rely on Docker running locally (or in Codespaces) directly.
To do this, add to features
:
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
To have it work also with plain Docker CLI, mount the Docker socket in the docker-compose.yaml
:
services:
node:
# ...
volumes:
- .:/workspace:cached
- /var/run/docker.sock:/var/run/docker.sock