docker

Docker Compose for Python/Django

Docker Compose config for Django with PostgreSQL, Celery worker, Redis message broker, and Gunicorn production server.

Overview

A Docker Compose setup for Django projects with PostgreSQL as the database, Redis as a Celery broker, a Celery worker for background tasks, and Gunicorn as the WSGI server. Optimized for development with auto-reload and volume mounts.

Configuration

# docker-compose.yml

services:
  # ── Django Web Application ──
  web:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: django-web
    restart: unless-stopped

    command: >
      sh -c "python manage.py migrate --noinput &&
             python manage.py runserver 0.0.0.0:8000"

    ports:
      - "8000:8000"

    environment:
      DJANGO_SETTINGS_MODULE: config.settings.development
      DATABASE_URL: postgresql://django:${DB_PASSWORD:-secret}@postgres:5432/djangodb
      REDIS_URL: redis://redis:6379/0
      SECRET_KEY: ${DJANGO_SECRET_KEY:-insecure-dev-key-change-in-prod}
      DEBUG: "True"
      ALLOWED_HOSTS: "*"

    volumes:
      - .:/app                         # Mount code for auto-reload
      - static_files:/app/staticfiles  # Collected static files
      - media_files:/app/media         # User-uploaded files

    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

    networks:
      - backend

  # ── Celery Worker ──
  celery:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: django-celery
    restart: unless-stopped

    command: celery -A config worker -l INFO --concurrency=2

    environment:
      DJANGO_SETTINGS_MODULE: config.settings.development
      DATABASE_URL: postgresql://django:${DB_PASSWORD:-secret}@postgres:5432/djangodb
      REDIS_URL: redis://redis:6379/0

    volumes:
      - .:/app

    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

    networks:
      - backend

  # ── Celery Beat (Periodic Tasks) ──
  celery-beat:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: django-celery-beat
    restart: unless-stopped
    profiles:
      - workers                        # Start with --profile workers

    command: celery -A config beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler

    environment:
      DJANGO_SETTINGS_MODULE: config.settings.development
      DATABASE_URL: postgresql://django:${DB_PASSWORD:-secret}@postgres:5432/djangodb
      REDIS_URL: redis://redis:6379/0

    volumes:
      - .:/app

    depends_on:
      - celery

    networks:
      - backend

  # ── PostgreSQL ──
  postgres:
    image: postgres:16-alpine
    container_name: django-postgres
    restart: unless-stopped

    environment:
      POSTGRES_USER: django
      POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
      POSTGRES_DB: djangodb

    ports:
      - "5432:5432"

    volumes:
      - postgres_data:/var/lib/postgresql/data

    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U django -d djangodb"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

    networks:
      - backend

  # ── Redis ──
  redis:
    image: redis:7-alpine
    container_name: django-redis
    restart: unless-stopped

    ports:
      - "6379:6379"

    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

    networks:
      - backend

volumes:
  postgres_data:
  static_files:
  media_files:

networks:
  backend:
    driver: bridge
# Dockerfile

FROM python:3.12-slim

# Prevent Python from writing .pyc files and enable unbuffered output
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

# Install system dependencies for psycopg2
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq-dev gcc && \
    rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy project files
COPY . .

EXPOSE 8000

CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "3"]

Key Options Explained

  • python manage.py migrate --noinput — Runs database migrations automatically on container start. Safe because Django migrations are idempotent.
  • celery -A config worker — Starts a Celery worker that imports tasks from the config app. --concurrency=2 limits to 2 parallel task threads.
  • DatabaseScheduler — Celery Beat stores periodic task schedules in the database so they can be managed via Django admin.
  • PYTHONDONTWRITEBYTECODE=1 — Prevents .pyc file clutter in volume-mounted directories during development.
  • PYTHONUNBUFFERED=1 — Ensures print statements and logs appear immediately in docker compose logs.

Common Modifications

  • Production setup: Replace runserver with gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 and set DEBUG=False.
  • Add Nginx: Place Nginx in front of Gunicorn to serve static files and handle SSL. Mount static_files volume in Nginx.
  • Add Flower: Include a Flower service for Celery monitoring: celery -A config flower --port=5555.
  • Use pip-tools: Replace requirements.txt with requirements.in and compile with pip-compile for reproducible builds.
  • Multi-stage build: Add a builder stage to compile dependencies, then copy only the virtual environment to the final image.