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
- Deploying With Gunicorn, Nginx, and systemd
- Testing Django Applications
- Docker installed — Linux Docker introduction
Dockerfile
Dockerfile:
Code explanation:
- Builder stage keeps final image smaller
libmariadb3formysqlclientruntime (adjust for PostgreSQL)USER appuser— do not run Gunicorn as root0.0.0.0— Docker port mapping
Entrypoint Script
docker/entrypoint.sh:
#!/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 "$@"chmod +x docker/entrypoint.shRuns migrations and static collection before Gunicorn starts—acceptable for single-node deploys; use one-off migration jobs for zero-downtime at scale.
.dockerignore
.venv/
__pycache__/
*.pyc
.git/
.env
db.sqlite3
media/
staticfiles/
logs/
.pytest_cache/
htmlcov/
*.mdDo not copy local venv or secrets into the image.
docker compose Stack
docker-compose.yml:
Run:
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
Django>=5.0,<6
djangorestframework
gunicorn
mysqlclient
django-redis
django-cors-headers
PillowPin versions in real projects for reproducible builds.
GitHub Actions CI
.github/workflows/django-test.yml:
Code explanation:
makemigrations --checkfails if model changes lack migrations- MySQL service optional—SQLite-only projects can skip
services: - Protect
mainbranch: require this check to pass before merge
See Git and CI/CD.
CI With SQLite Only (Simpler)
env:
DJANGO_SETTINGS_MODULE: config.settings.development
DJANGO_SECRET_KEY: ci-test-secret
steps:
- run: pip install -r requirements.txt
- run: python manage.py testUse separate settings/ci.py if CI needs different DATABASES.
Docker vs systemd on VPS
| Approach | When |
|---|---|
| systemd + venv | Single VPS, simple ops — chapter 23 |
| Docker compose | Reproducible dev/staging, multiple services |
| Kubernetes | Large scale — beyond this track |
Many teams build Docker images in CI, push to registry, pull on server.
Security in Containers
- Do not bake
SECRET_KEYinto Dockerfile—use env at runtime - Run as non-root (
appuser) - Scan images with
docker scoutor Trivy - Keep base image updated (
python:3.12-slim)
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Forgot migrate in entrypoint | OperationalError on start | Entrypoint or init container |
ALLOWED_HOSTS missing container host | 400 DisallowedHost | Add service name / domain |
Copy .env into image | Secret leak | Runtime env only |
collectstatic skipped | 404 CSS in container | Entrypoint collectstatic |
| CI uses prod settings | Dangerous side effects | development or ci settings |
Post-Chapter Checklist
-
Dockerfilebuilds and runs Gunicorn - Entrypoint runs
migrateandcollectstatic -
docker compose upserves the app with MySQL - GitHub Actions runs tests (and migration check)
-
.dockerignoreexcludes 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?
docker build -t ghcr.io/you/hello-django:latest .
docker push ghcr.io/you/hello-django:latest