Skip to main content

Django: Containerizing with Docker

Django: Containerizing with Docker

Containerizing a Django application with Docker streamlines development, testing, and deployment by packaging the application and its dependencies into portable, isolated containers. Built on Django’s Model-View-Template (MVT) architecture, this approach ensures consistency across environments, simplifies scaling, and integrates seamlessly with modern CI/CD pipelines. This guide covers best practices for containerizing Django with Docker, including Dockerfile setup, Docker Compose configuration, and production considerations, assuming familiarity with Django, Python, and basic Linux commands.


01. Why Containerize Django with Docker?

Docker encapsulates Django applications with their dependencies (e.g., Python, libraries, databases), eliminating "works on my machine" issues. It supports consistent environments for development and production, enables microservices architectures, and integrates with orchestration tools like Kubernetes. Containerization is ideal for deploying Django applications, such as APIs, e-commerce platforms, or content management systems, ensuring scalability and reproducibility while leveraging Python’s ecosystem.

Example: Basic Docker Check

# Verify Docker installation
docker --version

Output:

Docker version 27.3.1, build ce12230

Explanation:

  • Confirms Docker is installed and ready for containerizing the Django project.
  • Requires Docker and Docker Compose installed.

02. Key Containerization Components

Containerizing Django involves creating a Dockerfile for the application, using Docker Compose for multi-container setups (e.g., Django + PostgreSQL), and configuring production settings. The table below summarizes key components and their roles:

Component Description Purpose
Dockerfile Defines the Django app container Specifies dependencies and runtime
Docker Compose Manages multi-container services Orchestrates Django, database, etc.
settings.py Django configuration Adapts to containerized environment
Gunicorn WSGI server Serves Django in production
PostgreSQL Database service Handles persistent data


2.1 Prerequisites

  • Install Docker and Docker Compose.
  • A Django project with a Git repository.
  • Familiarity with Django settings and virtual environments.

Example: Verify Docker Compose

# Check Docker Compose version
docker compose version

Output:

Docker Compose version v2.29.2

Explanation:

  • Ensures Docker Compose is ready for multi-container orchestration.
  • Note: Use docker compose (v2) instead of docker-compose.

2.2 Create Dockerfile

Define the Django application container with a Dockerfile.

Example: Dockerfile Setup

# Dockerfile
FROM python:3.13-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Set working directory
WORKDIR /app

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

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

# Copy project
COPY . .

# Collect static files
RUN python manage.py collectstatic --noinput

# Run Gunicorn
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]

Output (build):

Successfully built django-app

Explanation:

  • FROM python:3.13-slim - Uses a lightweight Python base image.
  • PYTHONDONTWRITEBYTECODE - Prevents .pyc files for cleaner containers.
  • PYTHONUNBUFFERED - Ensures real-time logs.
  • RUN pip install - Installs dependencies from requirements.txt.
  • CMD - Starts Gunicorn for production.

2.3 Create requirements.txt

List project dependencies for the Docker container.

Example: Generate requirements.txt

# Install dependencies in virtual environment
pip install django gunicorn psycopg2-binary

# Generate requirements.txt
pip freeze > requirements.txt

Output (requirements.txt):

Django==5.1.3
gunicorn==23.0.0
psycopg2-binary==2.9.9

Explanation:

  • django - Core framework.
  • gunicorn - WSGI server for production.
  • psycopg2-binary - PostgreSQL adapter for database connectivity.

2.4 Configure Django Settings

Update settings.py for containerized environments.

Example: Production Settings

# myproject/settings.py
import os

DEBUG = os.environ.get('DJANGO_DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', 'localhost').split(',')
SECRET_KEY = os.environ.get('SECRET_KEY', 'your-fallback-secret-key')

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME', 'mydb'),
        'USER': os.environ.get('DB_USER', 'myuser'),
        'PASSWORD': os.environ.get('DB_PASSWORD', 'mypassword'),
        'HOST': os.environ.get('DB_HOST', 'db'),
        'PORT': os.environ.get('DB_PORT', '5432'),
    }
}

Explanation:

  • os.environ.get - Uses environment variables for flexibility.
  • ALLOWED_HOSTS - Supports multiple hosts via comma-separated list.
  • STATIC_ROOT - Collects static files for serving (e.g., by a reverse proxy).
  • DB_HOST='db' - Matches Docker Compose service name for PostgreSQL.

2.5 Set Up Docker Compose

Orchestrate Django and PostgreSQL services with Docker Compose.

Example: Docker Compose Configuration

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_DEBUG=False
      - SECRET_KEY=${SECRET_KEY}
      - ALLOWED_HOSTS=localhost,yourdomain.com
      - DB_NAME=mydb
      - DB_USER=myuser
      - DB_PASSWORD=mypassword
      - DB_HOST=db
      - DB_PORT=5432
    depends_on:
      - db
    volumes:
      - .:/app
      - staticfiles:/app/staticfiles
  db:
    image: postgres:16
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:
  staticfiles:

Explanation:

  • web - Django service built from the Dockerfile.
  • db - PostgreSQL service using the official Postgres image.
  • volumes - Persists database data and static files.
  • environment - Passes variables to Django and PostgreSQL.

2.6 Build and Run Containers

Build and start the application with Docker Compose.

Example: Run Docker Compose

# Build and start containers
docker compose up -d

# Run migrations
docker compose exec web python manage.py migrate

# Create superuser
docker compose exec web python manage.py createsuperuser

Output:

Starting myproject_db_1 ... done
Starting myproject_web_1 ... done
Migrations applied successfully.

Explanation:

  • docker compose up -d - Runs containers in detached mode.
  • exec web - Runs commands inside the Django container.
  • Access the app at http://localhost:8000.

2.7 Common Containerization Error

Example: Missing Dependency in Dockerfile

# Dockerfile (Incorrect)
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
docker compose up

Output:

web_1  | Error: No module named 'psycopg2'

Explanation:

  • Missing libpq-dev in Dockerfile causes psycopg2 installation failure.
  • Using runserver instead of Gunicorn is insecure for production.
  • Solution: Include system dependencies and use Gunicorn.

03. Effective Usage

3.1 Recommended Practices

  • Use a .env file for sensitive variables.

Example: Environment Variables

# .env
SECRET_KEY=your-secure-secret-key
DJANGO_DEBUG=False
ALLOWED_HOSTS=localhost,yourdomain.com
DB_NAME=mydb
DB_USER=myuser
DB_PASSWORD=mypassword
# docker-compose.yml (updated)
services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db
    volumes:
      - .:/app
      - staticfiles:/app/staticfiles

Output:

Environment variables loaded from .env
  • Add .env to .gitignore to prevent exposure.
  • Use multi-stage builds for smaller production images.

3.2 Practices to Avoid

  • Avoid running Django’s development server in production.

Example: Using Development Server

# Dockerfile (Incorrect)
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

Output:

web_1  | WARNING: This is a development server. Do not use it in a production deployment.
  • runserver is inefficient and insecure for production.
  • Solution: Use Gunicorn or another WSGI server.

04. Common Use Cases

4.1 Development Environment

Create a consistent development setup with Docker.

Example: Development Docker Compose

# docker-compose.dev.yml
version: '3.8'
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_DEBUG=True
      - DB_HOST=db
    volumes:
      - .:/app
    command: python manage.py runserver 0.0.0.0:8000
  db:
    image: postgres:16
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
docker compose -f docker-compose.dev.yml up

Output:

web_1  | Starting development server at http://0.0.0.0:8000/

Explanation:

  • Uses runserver for development with hot reloading.
  • Ensures consistent dependencies across team members.

4.2 Production Deployment with Nginx

Integrate Nginx as a reverse proxy for production.

Example: Nginx with Docker Compose

# docker-compose.prod.yml
version: '3.8'
services:
  web:
    build: .
    environment:
      - DJANGO_DEBUG=False
      - SECRET_KEY=${SECRET_KEY}
    volumes:
      - staticfiles:/app/staticfiles
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      - POSTGRES_DB=mydb
      - POSTGRES_USER=myuser
      - POSTGRES_PASSWORD=mypassword
    volumes:
      - postgres_data:/var/lib/postgresql/data
  nginx:
    image: nginx:1.25
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - staticfiles:/app/staticfiles
    depends_on:
      - web
volumes:
  postgres_data:
  staticfiles:
# nginx.conf
server {
    listen 80;
    server_name yourdomain.com;

    location /static/ {
        alias /app/staticfiles/;
    }

    location / {
        proxy_pass http://web:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Output:

nginx_1  | [notice] ready to process connections

Explanation:

  • nginx - Serves static files and proxies requests to Gunicorn.
  • Shared staticfiles volume ensures Nginx access.

Conclusion

Containerizing Django with Docker ensures consistent, scalable, and portable applications. Key takeaways:

  • Use a Dockerfile to package Django with Gunicorn and dependencies.
  • Orchestrate services with Docker Compose for Django and PostgreSQL.
  • Secure settings with environment variables and avoid development servers in production.
  • Integrate Nginx for production to handle static files and proxying.

With Docker, you can deploy Django applications efficiently, from local development to production environments! For more details, refer to the Docker Django guide.

Comments