Preamble

This tutorial aims to guide a beginner developer in setting up a containerized Django development environment, from initial installation to complete configuration.

System Prerequisites

1. Prior Installations

Before beginning, ensure you have installed the following tools:

For Ubuntu/Debian:

# System updates
sudo apt update && sudo apt upgrade -y

# Essential installations
sudo apt install -y \
    git \
    curl \
    wget \
    software-properties-common \
    apt-transport-https \
    ca-certificates \
    gnupg \
    lsb-release \
    postgresql-client \
    python3-pip \
    python3-venv \
    build-essential \
    libpq-dev

For other Linux distributions, adapt the commands:

  • Fedora/CentOS: dnf or yum
  • Arch Linux: pacman

2. Docker Installation

# Docker installation
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker compose-plugin

# Add your user to the docker group
sudo usermod -aG docker $USER

# Restart to apply changes
sudo systemctl restart docker

3. Verifications

# Verify installations
docker --version
docker compose --version
python3 --version
psql --version

Dockerized Django Project Structure

Creating Project Structure

# Create project directory
mkdir projet_django_docker
cd projet_django_docker

# Directory structure
mkdir -p app/mon_projet
touch app/Dockerfile
touch app/requirements.txt
touch app/entrypoint.sh
touch docker-compose.yml
touch .env

1. requirements.txt File

# app/requirements.txt
Django==4.2.7
psycopg2-binary==2.9.9
gunicorn==21.2.0
python-dotenv==1.0.0

2. Dockerfile

# app/Dockerfile
FROM python:3.11-slim

# Metadata
LABEL maintainer="Your Name <your.email@example.com>"

# Environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PYTHONPATH=/app

# Working directory
WORKDIR /app

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

# Copy and install requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the project
COPY . .

# Give permissions to entry script
RUN chmod +x /app/entrypoint.sh

# Entry point
ENTRYPOINT ["/app/entrypoint.sh"]

entrypoint.sh Script: Understanding Database Waiting

Why Wait for the Database?

#!/bin/bash
# Wait for database availability
echo "Waiting for database..."
while ! nc -z db 5432; do
  sleep 1
done
echo "Database ready!"

Technical Context

When starting a containerized application, services do not all start instantaneously. The PostgreSQL database can take a few seconds to be fully operational.

Concrete Problem

If your Django application attempts to connect to the database before it's ready, you'll encounter a connection error. This could block the complete startup of your application.

How Does It Work?

  • nc -z db 5432: Checks if port 5432 (PostgreSQL port) is accessible
  • while: Loops until connection is possible
  • sleep 1: One-second pause between each attempt

Complete Code with Migrations

#!/bin/bash
# Wait for database
echo "Waiting for database..."
while ! nc -z db 5432; do
  sleep 1
done
echo "Database ready!"

# Apply Django migrations
python manage.py migrate

# Launch web server
exec gunicorn --bind 0.0.0.0:8000 mon_projet.wsgi:application

Docker Compose: Understanding the Configuration

Structure of docker-compose.yml File

version: '3.8'  # Docker Compose syntax version

services:
  # Web Service (Django Application)
  web:
    build: 
      context: ./app
      dockerfile: Dockerfile
    volumes:
      - ./app:/app  # Source code synchronization
    ports:
      - "8000:8000"  # Port mapping
    env_file:
      - .env  # Environment file
    depends_on:
      - db  # Dependency with database service
    networks:
      - dev_network  # Custom network

  # Database Service
  db:
    image: postgres:15-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data  # Data persistence
    environment:
      - POSTGRES_DB=projet_db
      - POSTGRES_USER=projet_user
      - POSTGRES_PASSWORD=secret_password
    ports:
      - "5432:5432"  # PostgreSQL port exposure
    networks:
      - dev_network

# Volumes and Networks
volumes:
  postgres_data:  # Volume for PostgreSQL data

networks:
  dev_network:
    driver: bridge  # Network type

Detailed Explanations

1. Networks

  • dev_network: A custom network allowing communication between containers
  • driver: bridge: Creates an isolated network on the host

2. Ports: Internal vs External

  • Format: "8000:8000"
  • First port: External port (accessible from host)
  • Second port: Container's internal port

3. Service Dependencies

  • depends_on: - db means the web service will start after the database service
  • Guarantees startup order, not a "ready to use" guarantee

Accessing Container Terminals

# Access terminal of a specific service
docker compose exec web bash
docker compose exec db bash

Important Points

  • Works ONLY when containers are running
  • Allows performing actions directly in the container
  • Equivalent to SSH access for containers

Additional Tips

  • Recent Docker versions automatically detect Compose version
  • Always specify a context and a Dockerfile for more clarity
  • Use environment variables for sensitive configuration

Useful Commands

# Start services
docker compose up

# Start in detached mode
docker compose up -d

# Stop services
docker compose down

# View logs
docker compose logs

# Run a command in a service
docker compose run web python manage.py createsuperuser

Why Are These Details Important?

  • In-depth understanding of mechanisms
  • More efficient debugging
  • More flexible configuration
  • Better control of development environment

.env File

# Django Configuration
DEBUG=1
SECRET_KEY=your_ultra_secure_secret_key
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1

# Database Configuration
DB_NAME=projet_db
DB_USER=projet_user
DB_PASSWORD=secret_password
DB_HOST=db
DB_PORT=5432

Creating the Django Project

Project Initialization

# Create and initialize the Django project
docker compose run --rm web django-admin startproject mon_projet .

# Configure settings.py for the database
# Modify the DATABASES section:
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.getenv('DB_NAME', 'projet_db'),
        'USER': os.getenv('DB_USER', 'projet_user'),
        'PASSWORD': os.getenv('DB_PASSWORD', 'secret_password'),
        'HOST': os.getenv('DB_HOST', 'db'),
        'PORT': os.getenv('DB_PORT', '5432'),
    }
}

Starting and Using

Essential Commands

# Build and start containers
docker compose up --build

# Stop containers
docker compose down

# Create a new application
docker compose run --rm web python manage.py startapp my_new_app

# Create migrations
docker compose run --rm web python manage.py makemigrations

# Apply migrations
docker compose run --rm web python manage.py migrate

Tips and Best Practices

  1. Security:
    • Never commit the .env file
    • Use a .env.example with dummy values
    • Generate robust secret keys
  2. Performance:
    • Use volumes for development
    • Optimize Docker images
    • Manage dependencies carefully
  3. Development:
    • Use docker-compose.override.yml for specific configurations
    • Set up an adapted git workflow

Troubleshooting

  • Check logs: docker compose logs
  • Check container status: docker compose ps
  • Connect to a container: docker compose exec web bash

Possible Evolutions

  • Add a Redis/Celery container
  • Set up an Nginx proxy
  • Configure a volume for static files