Saltar al contenido principal

Clase 06 — YAML en Docker y Docker Compose

Estructura de un docker-compose.yml

version: "3.8"          # Versión del formato Compose

services: # Definición de servicios (contenedores)
nombre_servicio:
image: imagen:tag

volumes: # Volúmenes persistentes
nombre_volumen:

networks: # Redes personalizadas
nombre_red:

secrets: # Secretos (Docker Swarm)
nombre_secreto:

configs: # Configuraciones externas
nombre_config:

Servicio básico

version: "3.8"

services:
web:
image: nginx:1.25-alpine
ports:
- "80:80"
restart: unless-stopped

Build desde Dockerfile

services:
api:
# Build simple
build: ./backend

# Build con opciones
build:
context: ./backend
dockerfile: Dockerfile
args:
APP_VERSION: "2.0"
NODE_ENV: production
target: production # Multi-stage build
cache_from:
- mi-app:latest

Puertos

services:
app:
image: mi-app:latest
ports:
# HOST:CONTAINER
- "8080:80" # Puerto 8080 del host → 80 del container
- "443:443" # Mismo puerto
- "127.0.0.1:3000:3000" # Solo accesible desde localhost
- "9090-9092:9090-9092" # Rango de puertos

# Exponer sin publicar (solo entre contenedores)
expose:
- "6379"
- "9200"

Variables de entorno

services:
app:
image: mi-app:latest

# Forma 1: Lista
environment:
- NODE_ENV=production
- DB_HOST=postgres
- SECRET_KEY=${MI_SECRET} # Desde variable del host

# Forma 2: Mapa (más legible)
environment:
NODE_ENV: production
DB_HOST: postgres
DB_PORT: "5432"
SECRET_KEY: ${MI_SECRET}

# Forma 3: Archivo externo
env_file:
- .env
- .env.production

Volúmenes

services:
app:
image: mi-app:latest
volumes:
# Bind mount (directorio del host)
- ./src:/app/src
- ./config/nginx.conf:/etc/nginx/nginx.conf:ro # read-only

# Named volume
- app_data:/app/data

# Tmpfs (en memoria)
- type: tmpfs
target: /app/tmp

postgres:
image: postgres:16
volumes:
- pg_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

# Declarar named volumes
volumes:
app_data:
driver: local
pg_data:
driver: local

Redes

services:
frontend:
image: react-app:latest
networks:
- frontend-net

api:
image: api:latest
networks:
- frontend-net # Accesible por frontend
- backend-net # Accesible por BD

postgres:
image: postgres:16
networks:
- backend-net # Solo accesible por API

networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true # Sin acceso a internet

Dependencias y healthchecks

services:
api:
image: mi-api:latest
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s

postgres:
image: postgres:16
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3

Recursos y limits

services:
api:
image: mi-api:latest
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
replicas: 3
restart_policy:
condition: on-failure
delay: 5s
max_attempts: 3

Ejemplo completo: Stack de producción

# docker-compose.prod.yml
version: "3.8"

x-common: &common
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"

services:
# ─── NGINX (Reverse Proxy) ───
nginx:
<<: *common
image: nginx:1.25-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- static_files:/var/www/static:ro
depends_on:
api:
condition: service_healthy
networks:
- frontend

# ─── API (Backend) ───
api:
<<: *common
build:
context: ./backend
target: production
environment:
DATABASE_URL: "postgresql://app:${DB_PASSWORD}@postgres:5432/app_db"
REDIS_URL: "redis://redis:6379/0"
SECRET_KEY: ${SECRET_KEY}
APP_ENV: production
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
- static_files:/app/static
networks:
- frontend
- backend
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"

# ─── WORKER (Background Jobs) ───
worker:
<<: *common
build:
context: ./backend
target: production
command: celery -A app worker --loglevel=info
environment:
DATABASE_URL: "postgresql://app:${DB_PASSWORD}@postgres:5432/app_db"
REDIS_URL: "redis://redis:6379/0"
depends_on:
- postgres
- redis
networks:
- backend

# ─── POSTGRESQL ───
postgres:
<<: *common
image: postgres:16-alpine
environment:
POSTGRES_DB: app_db
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app_db"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend

# ─── REDIS ───
redis:
<<: *common
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 256mb
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
networks:
- backend

volumes:
pg_data:
redis_data:
static_files:

networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true

Comandos útiles de Docker Compose

docker compose up -d                  # Levantar servicios
docker compose up -d --build # Levantar con build
docker compose logs -f api # Ver logs
docker compose up -d --scale api=3 # Escalar servicios
docker compose ps # Ver estado
docker compose down # Detener todo
docker compose down -v # Detener y eliminar volúmenes
docker compose config # Validar el archivo

Ejercicios

  1. Creá un docker-compose.yml para una app con: Nginx, API (FastAPI), PostgreSQL y Redis
  2. Agregá healthchecks a todos los servicios
  3. Usá anclas para evitar duplicación en la configuración común
  4. Creá un archivo .env con las variables sensibles y referencialas desde el compose