Django in Dev Containers

Mike Gouline
5 min readSep 21, 2024

--

Applications that depend on databases and other services make for fiddly local setup and Docker Compose is a common solution, but what about IDE integration? This article shows how you can develop Django applications entirely within a container using VS Code and Dev Containers.

Just want to see the code? Help yourself to GitHub.

Docker

Before configuring VS Code, we need a working Docker Compose stack. While Docker alone is sufficient, a multiple-container setup is a more compelling proposition to demonstrate. Skip ahead if you already have your own Dockerfile and docker-compose.yaml.

Django specifics are out of scope, I will assume you have an existing application or you can use my example on GitHub (inspired by the polls tutorial).

Dockerfile

Let’s start with a simple Debian-based Python image and inline pip.

FROM python:3.12
WORKDIR /app
RUN pip install django psycopg
COPY . ./
ENTRYPOINT ["python3", "manage.py"]

Feel free to make this fancier while keeping in mind that non-Debian images may require some tweaking.

docker-compose.yaml

We can add PostgreSQL with a health check, Django migration, and — most importantly — the app service that runs our Django server and VS Code will connect to. Repository root is mounted to /app in the container.

name: django-devcontainer

services:
postgres:
image: postgres:16
ports:
- 5432:5432
env_file:
- docker.env
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
restart: always

migration:
build: .
command: migrate
env_file:
- docker.env
depends_on:
postgres:
condition: service_healthy

app:
build: .
command: runserver 0.0.0.0:8080
ports:
- 8080:8080
env_file:
- docker.env
volumes:
- .:/app
depends_on:
postgres:
condition: service_healthy
migration:
condition: service_completed_successfully

You can validate everything works by running docker compose up.

Dev Containers

We can start configuring VS Code to connect to our Docker Compose stack:

  1. Ensure Dev Containers extension is installed and enabled;
  2. Create an empty .devcontainer/devcontainer.json file — subsequent sections will fill it with functionality incrementally, producing a working setup at each step (in true Agile™ fashion).

Basics

Here’s a minimum working devcontainer.json setup:

{
// Path to Docker Compose file(s)
"dockerComposeFile": "../docker-compose.yaml",
// Which service inside dockerComposeFile to attach to
"service": "app",
// Attach directory within the service container
"workspaceFolder": "/app"
}

VS Code should prompt you to reopen your project in a container, otherwise you can search commands for Dev Containers: Reopen in Container. Once it builds and connects, your title bar should look like this:

Congratulations, you are now developing inside the container!

Extensions

After the excitement wears off, you will realise there’s more work to do before this containerised environment is ready for Python development. Let’s install and configure your VS Code extensions:

{
...

// VS Code customizations
"customizations": {
"vscode": {
// Extension identifiers to install
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.debugpy",
"charliermarsh.ruff",
"batisteo.vscode-django"
]
},
// Settings from your workspace/project settings.json
"settings": {
// Default Python interpreter inside the container
"python.defaultInterpreterPath": "/usr/local/bin/python3",
// Django manage.py unit testing arguments
"python.testing.unittestArgs": ["--no-input"],
// Enable unittest-based Python tests
"python.testing.unittestEnabled": true
}
}

These are my favourites, you can add others from the Marketplace by their identifiers (shown under “More Info”).

This minimal setup configures Python and unit testing, but you need to set MANAGE_PY_PATH environment variable to run Django unit tests:

{
...

// Environment variables inside the container
"containerEnv": {
"MANAGE_PY_PATH": "./manage.py"
}
}

While you can alternatively set environment variables in Dockerfile, docker-compose.yaml or elsewhere, given this one is only used by VS Code, that’s where I prefer to keep it.

After making these changes to devcontainer.json, you will be prompted to rebuild to apply them, otherwise you can search commands for Dev Containers: Rebuild Container. Once completed, your tests should now be runnable under Testing in the side bar:

Git

We can write and test code, now we need to commit it to Git. Add the following to your devcontainer.json to install Git and Vim (for editing commit messages):

{
...

"features": {
// Install git for your dev environment
"ghcr.io/devcontainers/features/git:1": {},
// Install vim for git commit messages
"ghcr.io/jungaretti/features/vim:1": {}
}
}

Once again, you can install packages directly in the container, this just gives you a nice abstraction for tools only used in VS Code.

Other features are available here, you can even contribute your own.

Your .gitconfig should already be passed through to the container, but SSH credentials need to be exposed manually:

{
...

"mounts": [
// Expose ~/.ssh to the container (read only)
"source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind,ro,consistency=cached"
]
}

See Docker documentation for more information about mounts, if you want to share anything else on the host machine with your container.

Bash

What if we want shell creature comforts, such as completions and custom prompts? There’s a feature for that too!

Let’s create .bashrc with your preferences in ~/.config/devcontainer:

# Bash completion for Git
if [ -f /usr/share/bash-completion/completions/git ]; then
source /usr/share/bash-completion/completions/git
fi

# Custom prompt with current Git branch
if [ -f /usr/lib/git-core/git-sh-prompt ]; then
source /usr/lib/git-core/git-sh-prompt
PS1='\[\033[01;32m\]➜\[\033[0m\] \[\033[36m\]\W\[\033[0m\]\[\033[01;31m\]$(__git_ps1 " (%s)")\[\033[0m\] \$ '
fi

See Git documentation for more information on what’s happening here.

Now we need to add bash-profile feature and a corresponding mount for that ~/.config/devcontainer directory:

{
...

"features": {
...

// Source .bashrc under ~/.config/devcontainer mount (if exists)
"ghcr.io/eliises/devcontainer-features/bash-profile:1": {
"command": "test -f /devcontainer/.bashrc && . /devcontainer/.bashrc"
}
}

"mounts": [
...

// Other optional container configurations
"source=${localEnv:HOME}/.config/devcontainer,target=/devcontainer,type=bind,ro,consistency=cached"
]
}

Your container’s terminal feels like home without forcing your aesthetic choices on your colleagues, since .bashrc lives outside the repository!

Conclusion

This guide walked you through a working setup for Django development in Dev Containers, see documentation for more devcontainer.json options or simply use autocompletion and tooltips in VS Code. While some extensions were specific to Python, you can recycle everything else for containerised projects in other languages as well.

Hopefully, this saves you some yak shaving and improves your workflow!

--

--