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
- Creá un
docker-compose.ymlpara una app con: Nginx, API (FastAPI), PostgreSQL y Redis - Agregá healthchecks a todos los servicios
- Usá anclas para evitar duplicación en la configuración común
- Creá un archivo
.envcon las variables sensibles y referencialas desde el compose