Ir al contenido

IDEAS PRÁCTICAS · ASIMOV INGENIERÍA

Docker compose + Django

Imagen ilustrativa · Growtika / Unsplash.

Table of contents

No titles in the blog post

De la manera más general posible, Docker Compose es una herramienta que te permite definir y ejecutar aplicaciones complejas que utilizan múltiples contenedores de Docker como si fueran una sola unidad. En lugar de iniciar cada contenedor por separado con comandos individuales, Docker Compose centraliza la configuración de todos los servicios de tu aplicación (por ejemplo, un servidor web, una base de datos y una caché) en un único archivo de configuración.

Equipos y cableado en un rack de servidores
Imagen ilustrativa · Kevin Ache / Unsplash.

Propósito principal

Su objetivo es simplificar el proceso de desarrollo, pruebas y despliegue de aplicaciones, permitiendo a los desarrolladores:

  • Definir la arquitectura de la aplicación: Todo se especifica en un archivo YAML simple y legible.
  • Automatizar el lanzamiento: Un solo comando (docker-compose up) crea, inicia y conecta todos los servicios.
  • Garantizar la consistencia: La configuración versionada permite que cualquiera levante el proyecto igual con un solo comando.

Repaso rápido

  • Imagen vs Contenedor: La imagen es la receta; el contenedor es su instancia en ejecución.
  • Compose: Orquesta múltiples servicios, redes y volúmenes desde un solo YAML.
  • Red interna (DNS): Compose crea una red donde los servicios se resuelven por nombre.
  • Volúmenes: Persisten datos más allá del ciclo de vida del contenedor.
  • Variables de entorno: Evitan hardcodear credenciales.
  • Healthcheck & depends_on: Asegura que un servicio espere a otro antes de arrancar.

Ejemplo práctico

En este ejercicio vamos a:

  • Crear dos servicios en Docker: db (Postgres) y web (Django)
  • Conectarlos por red interna
  • Persistir datos con volúmenes
  • Correr migraciones y levantar el servidor automáticamente

Crearemos una carpeta llamada django-docker-demo con la siguiente estructura:

django-docker-demo/
├─ docker-compose.yml
├─ .env
└─ web/
   ├─ Dockerfile
   ├─ requirements.txt
   └─ (luego se generará aquí el proyecto Django)

Archivo docker-compose.yml

version: "3.9"

services:
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

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

    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 10

  web:
    build: ./web

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

    environment:
      DJANGO_DEBUG: "1"
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_HOST: db
      POSTGRES_PORT: "5432"

    depends_on:
      db:
        condition: service_healthy

    ports:
      - "8000:8000"

    volumes:
      - ./web:/app

volumes:
  postgres_data:

Resumen

  • services: Define los contenedores.
  • db: Base de datos con persistencia y healthcheck.
  • web: Django con dependencias, puertos y volumen.
  • depends_on: Asegura el orden correcto de arranque.
  • environment: Separa credenciales y configuración.

Archivo .env

POSTGRES_DB=mydb
POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword

Archivo Dockerfile dentro de web/

FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt /app/requirements.txt

RUN pip install --no-cache-dir -r requirements.txt

COPY . /app

Archivo requirements.txt

Django>=5.1,<5.2
psycopg2-binary>=2.9
gunicorn>=21.2

Crea el proyecto y la app usando contenedores

docker compose run --rm web django-admin startproject mysite .
docker compose run --rm web python manage.py startapp notes

Script wait_for_db.py

import os, time
import psycopg2
from psycopg2 import OperationalError

host = os.getenv("POSTGRES_HOST", "db")
db = os.getenv("POSTGRES_DB")
user = os.getenv("POSTGRES_USER")
password = os.getenv("POSTGRES_PASSWORD")
port = int(os.getenv("POSTGRES_PORT", "5432"))

while True:
    try:
        conn = psycopg2.connect(
            dbname=db, user=user, password=password, host=host, port=port
        )
        conn.close()
        print("✅ Database is ready.")
        break
    except OperationalError as e:
        print("⌛ Waiting for database...", e)
        time.sleep(1)

Configurar Postgres en Django

En mysite/settings.py:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('POSTGRES_DB'),
        'USER': os.environ.get('POSTGRES_USER'),
        'PASSWORD': os.environ.get('POSTGRES_PASSWORD'),
        'HOST': os.environ.get('POSTGRES_HOST', 'db'),
        'PORT': os.environ.get('POSTGRES_PORT', '5432'),
    }
}

Modelo de ejemplo

En web/notes/models.py:

from django.db import models

class Note(models.Model):
    text = models.CharField(max_length=200)
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.text[:50]

Migraciones

docker compose run --rm web python manage.py makemigrations
docker compose run --rm web python manage.py migrate

Levantar el stack

# Construir imagen
docker compose build

# Levantar en primer plano
docker compose up

# Segundo plano
docker compose up -d

# Logs
docker compose logs -f web
docker compose logs -f db

# Crear superusuario
docker compose run --rm web python manage.py createsuperuser

# Apagar (conservar volumen)
docker compose down

# Apagar y borrar datos
docker compose down -v

Debug

docker compose exec web bash

docker compose exec web python -c "
import psycopg2, os;
print(psycopg2.connect(
 dbname=os.getenv('POSTGRES_DB'),
 user=os.getenv('POSTGRES_USER'),
 password=os.getenv('POSTGRES_PASSWORD'),
 host=os.getenv('POSTGRES_HOST'),
 port=int(os.getenv('POSTGRES_PORT', '5432'))
))
"