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.
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) yweb(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'))
))
"