I have an existing Django project using docker compose
and I am trying to add devcontainer support. I have it mostly working except there is still one thing that is bothering me.
In my docker-compose.yml file, I have a service defined called web
. I want to expand the docker image created for the web
service to add more dev features. I have created the .devcontainer
directory with a devcontainer.json
file. I also have a docker-compose.yml
file in .devcontainer
which defines the dev
service. I have a Dockerfile
in .devcontainer
that I want to extend from the web
image.
The problem I am running into is when the devcontainer system in vscode tries to build the containers, the web
container doesn't exist so building the dev
container fails. I have to manually build the web
container first so the image exists, and then the dev
container build will work.
This is my .devcontainer/devcontainer.json
file.
{
"name": "Existing Docker Compose (Extend)",
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [
"../docker-compose.yml",
"../docker-compose-development.yml",
"docker-compose.yml"
],
// The 'service' property is the name of the service for the container that VS Code should
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
"service": "dev",
// The optional 'workspaceFolder' property is the path VS Code should open by default when
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
"workspaceFolder": "/workspace",
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line if you want start specific services in your Docker Compose config.
// "runServices": [],
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
// "shutdownAction": "none",
// Uncomment the next line to run commands after the container is created.
// "postCreateCommand": "bash pip install pre-commit"
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
"remoteUser": "vscode",
"postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder} && cd sas_applications && poetry install && cd ..",
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.azure-repos",
"vscodevim.vim",
"ms-python.python",
"ms-python.isort",
"ms-python.black-formatter"
]
}
}
}
and my .devcontainer/docker-compose.yml
file
services:
# Update this to the name of the service you want to work with in your docker-compose.yml file
dev:
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
# folder. Note that the path of the Dockerfile and context is relative to the *primary*
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
# array). The sample below assumes your primary file is in the root of your project.
#
build:
context: .
dockerfile: .devcontainer/Dockerfile
# Overrides default command so things don't shut down after the process ends.
entrypoint: []
command: /bin/sh -c "while sleep 1000; do :; done"
and .devcontainer/Dockerfile
FROM web
ARG UID=1000
ARG GID=1000
RUN apt install -y pipx zsh
RUN groupadd --gid "${GID}" vscode && useradd -u "${UID}" -g "${GID}" -s /usr/bin/zsh -m vscode
USER vscode
RUN sh -c "$(wget -O- https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
RUN sed -i '/plugins=/ s/=.*/=\(git python\)/' ~/.zshrc
RUN pipx install invoke && pipx ensurepath
WORKDIR /workspace/sas_applications
CMD ["sleep", "infinity"]
I have looked into multiple options but can't come up with a solution. I would prefer not duplicating the web
dockerfile in the dev
dockerfile. I would also prefer not to make the developer run a manual docker-compose build
step.
I thought about trying to define the dev
service in the base docker-compose.yml
file where the web
service is defined and make use of the profile
feature. However, I don't know how to tell vscode devcontainers to add that profile argument to the build command.
Thanks in advance.
After more research, I have found a decent solution. This solution makes use of multi-stage builds in Docker. This stackoverflow answer gave me the clue that got me pointed in the right direction.
The first change was to add multi-stage builds to the existing Dockerfile.
FROM python:3.11 as base
# Do the original image build instructions
FROM base as dev
# Do the build steps needed to add any
# development tools or configuration
Then, in the original docker-compose.yml file, set the build target to be the base
image.
services:
web:
build:
...
target: base
...
Then, in the .devcontainer/docker-compose.yml
file, set the build target to be the dev
image.
services:
web:
build:
...
target: dev
...
Now, when building the images, you can specify which target to build just by changing the docker-compose files included at the command line. To build for deployment, use docker compose -f docker-compose.yml
.
{
"name": "Existing Docker Compose (Extend)",
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],
...
The .devcontainer/devcontainer.json
file will specify to include both docker compose files. vscode will now build the dev
container which includes the additional development tools and configuration.