Django Docker and CI

Introduction

Docker packages Django with its dependencies into a reproducible image; docker compose runs the web app alongside MySQL and Redis. GitHub Actions runs python manage.py test on every push so broken migrations or failing views never merge silently. This chapter builds a production-oriented Dockerfile, a compose stack, and a CI workflow linked to the Git CI/CD track.

Prerequisites

Dockerfile

Dockerfile:

Code explanation:

  • Builder stage keeps final image smaller
  • libmariadb3 for mysqlclient runtime (adjust for PostgreSQL)
  • USER appuser — do not run Gunicorn as root
  • 0.0.0.0 — Docker port mapping

Entrypoint Script

docker/entrypoint.sh:

bash
#!/bin/bash
set -e
 
export DJANGO_SETTINGS_MODULE="${DJANGO_SETTINGS_MODULE:-config.settings.production}"
 
python manage.py migrate --noinput
python manage.py collectstatic --noinput
 
exec "$@"
bash
chmod +x docker/entrypoint.sh

Runs migrations and static collection before Gunicorn starts—acceptable for single-node deploys; use one-off migration jobs for zero-downtime at scale.

.dockerignore

dockerignore
.venv/
__pycache__/
*.pyc
.git/
.env
db.sqlite3
media/
staticfiles/
logs/
.pytest_cache/
htmlcov/
*.md

Do not copy local venv or secrets into the image.

docker compose Stack

docker-compose.yml:

Run:

bash
docker compose up --build
curl http://127.0.0.1:8000/health/

Production: put Nginx in front of web:8000, use secrets manager for env vars, not plain compose literals.

requirements.txt Sample

text
Django>=5.0,<6
djangorestframework
gunicorn
mysqlclient
django-redis
django-cors-headers
Pillow

Pin versions in real projects for reproducible builds.

GitHub Actions CI

.github/workflows/django-test.yml:

Code explanation:

  • makemigrations --check fails if model changes lack migrations
  • MySQL service optional—SQLite-only projects can skip services:
  • Protect main branch: require this check to pass before merge

See Git and CI/CD.

CI With SQLite Only (Simpler)

yaml
env:
  DJANGO_SETTINGS_MODULE: config.settings.development
  DJANGO_SECRET_KEY: ci-test-secret
 
steps:
  - run: pip install -r requirements.txt
  - run: python manage.py test

Use separate settings/ci.py if CI needs different DATABASES.

Docker vs systemd on VPS

ApproachWhen
systemd + venvSingle VPS, simple ops — chapter 23
Docker composeReproducible dev/staging, multiple services
KubernetesLarge scale — beyond this track

Many teams build Docker images in CI, push to registry, pull on server.

Security in Containers

  • Do not bake SECRET_KEY into Dockerfile—use env at runtime
  • Run as non-root (appuser)
  • Scan images with docker scout or Trivy
  • Keep base image updated (python:3.12-slim)

Common Mistakes

MistakeSymptomFix
Forgot migrate in entrypointOperationalError on startEntrypoint or init container
ALLOWED_HOSTS missing container host400 DisallowedHostAdd service name / domain
Copy .env into imageSecret leakRuntime env only
collectstatic skipped404 CSS in containerEntrypoint collectstatic
CI uses prod settingsDangerous side effectsdevelopment or ci settings

Post-Chapter Checklist

  • Dockerfile builds and runs Gunicorn
  • Entrypoint runs migrate and collectstatic
  • docker compose up serves the app with MySQL
  • GitHub Actions runs tests (and migration check)
  • .dockerignore excludes venv and secrets

FAQ## FAQ

Migrations in entrypoint vs manual?

Entrypoint fine for small apps; production clusters often run migrate as separate Job.

Static files in Docker?

Volume or Nginx sidecar serving static_volume; or WhiteNoise in Gunicorn container.

Test database in CI?

Django creates test_* DB automatically; ensure MySQL user can create databases.

docker compose watch?

Django dev: mount source + runserver in override docker-compose.override.yml.

Push image to registry?

bash
docker build -t ghcr.io/you/hello-django:latest .
docker push ghcr.io/you/hello-django:latest