Sarah Ting

Django dev docker compose

In the Laravel ecosystem, there’s a first party development environment called Laravel Sail which takes care of your Docker containers. This is super useful for local development.

  • It comes with a bunch of supported containers out of the box which might be useful for local dev (php of course, mysql/postgres, redis, meilisearch, mailhog, selenium, etc)
  • It provides the sail command line convenience scripts for reaching into your containers to do common tasks
  • It comes with a neat sail share command which is similar to an ngrok wrapper that will pop your development site up at a subdomain of your choice attached to [laravel-sail.site](http://laravel-sail.site) (great for testing webhooks)

When I popped my first Django project up, I was looking for Django’s version of Sail, but it doesn’t seem like there’s a first party equivalent. I’ve been getting the impression Django is more light-weight and unopinionated, and doesn’t include features which I’m used to coming with Laravel either as an out of the box or first-party solution.

Some other examples I’ve come across include Laravel’s queue/scheduler (the Django community seems to recommend options such as Celery, Django Q, RQ, which I’ll have to try out) and Laravel Passport (this seems to come in the third party Django OAuth Toolkit), but it seems likely I’ll run into more. This isn’t a big deal; it just means that I need to take the extra step of researching which choice is best instead of just using the tool recommended to me by the framework.

I ended up patching a docker compose and some convenience scripts together with help from Docker’s Awesome Compose, and I suppose I’ll just use free ngrok and deal with the randomised subdomains for now.

# docker-compose.yml
services:
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
      - /code/frontend/dist
      - /code/frontend/node_modules
    ports:
      - "8000:8000"
    environment:
      - POSTGRES_NAME=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    env_file:
      - ".env"
    depends_on:
      - db
  db:
    image: postgres
    volumes:
      - ./data/db:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
  npm:
    image: node:19.3-alpine
    volumes:
      - ./frontend:/usr/src/app
    working_dir: /usr/src/app
    ports:
      - "5173:5173"
    profiles:
      - donotstart
# syntax=docker/dockerfile:1

FROM node:19.3-alpine As npm
WORKDIR /usr/src/app
COPY frontend/ ./
RUN npm install
RUN mode=production npm run build

FROM python:3 as production
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
COPY --from=npm /usr/src/app/ ./frontend

Convenience script for easy development —

#!/usr/bin/env bash

EXEC_WEB="docker compose exec web "
EXEC_NPM="docker compose run npm "

if [ $# -gt 0 ];then
    if [ "$1" == "manage" ]; then
        shift 1
        $EXEC_WEB python3 manage.py "$@"
        
    elif [ "$1" == "pip" ]; then
        shift 1
        $EXEC_WEB pip "$@"
        
    elif [ "$1" == "npm" ]; then
        shift 1
        $EXEC_NPM npm "$@"

    elif [ "$1" == "dev" ]; then
        $EXEC_NPM npm install
        docker compose run -p 5173:5173 npm npm run dev

    elif [ "$1" == "build" ]; then
        $EXEC_NPM npm install 
        docker compose run -e mode=production npm npm run build

    else
        $EXEC_WEB "$@"
    fi
fi

I named this helper script d and my development flow seems pretty OK so far!

  • docker compose up will first put up the webserver and database
  • d manage can be used for passing commands to [manage.py](http://manage.py) — eg. d manage startapp frontend
  • d pip can be used for pip — eg. d pip install djangorestframework
  • d npm for npm commands — eg. d npm i axios
  • d dev for putting the vite server up at http://localhost:5173
  • d build to build vite
  • The npm commands spin up a temporary container with a volume mounted to the frontend folder instead of exec-ing on an existing container (since I don’t have the vite server running at all times)

Pretty fun detour! ☺️