Docker Compose for Django development by Simon Willison

Introduction

I had to get Docker Compose working for a Django project, primarily to make it easier for other developers to get a working development environment.

Some features of this project

  • Uses GeoDjango, so needs GDAL etc for the Django app plus a PostgreSQL server running PostGIS

  • Already has a Dockerfile used for the production deployment, but needed a separate one for the development environment

  • Makes extensive use of Django migrations (over 100 and counting)

I ended up with this docker-compose.yml file in the root of the project:

 1 version: "3.1"
 2
 3 services:
 4   database:
 5     image: postgis/postgis:13-3.1
 6     restart: always
 7     expose:
 8       - "5432"
 9     ports:
10       - "5432:5432"
11     environment:
12       POSTGRES_USER: postgres
13       POSTGRES_DB: mydb
14       POSTGRES_PASSWORD: postgres
15   web:
16     container_name: myapp
17     build:
18       context: .
19       dockerfile: Dockerfile.dev
20     command: python manage.py runserver 0.0.0.0:3000
21     environment:
22       DATABASE_URL: postgres://postgres:postgres@database:5432/mydb
23       DEBUG: 1
24     volumes:
25       - .:/app
26     ports:
27       - "3000:3000"
28     depends_on:
29       - migrations
30       - database
31   migrations:
32     build:
33       context: .
34       dockerfile: Dockerfile.dev
35     command: python manage.py migrate --noinput
36     environment:
37       DATABASE_URL: postgres://postgres:postgres@database:5432/mydb
38     volumes:
39       - .:/app
40     depends_on:
41       - database

The db container runs PostGIS.

The web container runs the Django development server, built using the custom Dockerfile.dev Dockerfile.

The migrations container simply runs the apps migrations and then terminates - with depends_on used to ensure that migrations run after the hdatabase server starts and before the web server.

The container_name: myapp field on the web container is a convenience which means you can later run commands like this:

docker exec -it myapp ./manage.py collectstatic

Here’s Dockerfile.dev:

 1 FROM python:3.9-slim
 2
 3 ENV APP_HOME /app
 4 WORKDIR $APP_HOME
 5
 6 ENV PYTHONUNBUFFERED 1
 7
 8 # gdal for GeoDjango
 9 RUN apt-get update && apt-get install -y \
10     binutils \
11     gdal-bin \
12     libproj-dev \
13     git \
14     && rm -rf /var/lib/apt/lists/*
15
16 COPY requirements.txt .
17 RUN pip install --no-cache-dir -r requirements.txt
18
19 WORKDIR $APP_HOME/myapp
20
21 RUN ./manage.py collectstatic --no-input