De Contenedor Único a Stack Completo
En la lección anterior, aprendiste a ejecutar tests en un solo contenedor Docker. Pero las aplicaciones reales raramente se ejecutan aisladas. Una aplicación web típica necesita una base de datos, un caché, posiblemente una cola de mensajes y quizás un servicio de email. Docker Compose te permite definir todo esto como un solo stack que inicia y se detiene junto.
Para QA, Docker Compose es transformador. En lugar de configurar manualmente PostgreSQL, Redis y la aplicación antes de ejecutar tests de integración, defines todo en un archivo docker-compose.yml y lo inicias con un solo comando.
Fundamentos de Docker Compose
Ejemplo Mínimo
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://test:test@db:5432/testdb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
db:
image: postgres:15
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
Iniciando y Deteniendo
# Iniciar todos los servicios
docker compose up -d
# Iniciar y reconstruir imágenes
docker compose up -d --build
# Ver logs
docker compose logs -f
# Detener y eliminar contenedores
docker compose down
# Detener, eliminar contenedores Y volúmenes (estado limpio)
docker compose down -v
Patrones de Entornos de Test
Patrón 1: App + Dependencias + Test Runner
El patrón más común para testing de integración y E2E:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=test
- DATABASE_URL=postgresql://test:test@db:5432/testdb
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
volumes:
- ./scripts/init-test-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
timeout: 5s
retries: 5
tests:
build:
context: .
dockerfile: Dockerfile.tests
environment:
- BASE_URL=http://app:3000
- DATABASE_URL=postgresql://test:test@db:5432/testdb
depends_on:
app:
condition: service_started
volumes:
- ./test-results:/app/test-results
Patrón 2: Stack de Integración Completo
version: '3.8'
services:
app:
build: .
environment:
- DATABASE_URL=postgresql://test:test@db:5432/testdb
- REDIS_URL=redis://cache:6379
- MAIL_HOST=mailhog
- MAIL_PORT=1025
- S3_ENDPOINT=http://localstack:4566
depends_on:
db:
condition: service_healthy
db:
image: postgres:15
environment:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
mailhog:
image: mailhog/mailhog
ports:
- "8025:8025"
localstack:
image: localstack/localstack
environment:
- SERVICES=s3
- DEFAULT_REGION=us-east-1
Health Checks
Los health checks son esenciales para la confiabilidad de los tests. Sin ellos, los tests pueden iniciar antes de que la base de datos esté lista para aceptar conexiones.
db:
image: postgres:15
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s
Ejercicio: Construye un Entorno de Test Completo
Diseña un docker-compose.yml para una aplicación de e-commerce con:
- Backend API Node.js
- Base de datos PostgreSQL (con datos semilla)
- Caché Redis
- Servicio de email (para confirmaciones de pedido)
- Test runner para tests API y E2E
- Resultados de test guardados en la máquina host
Solución
version: '3.8'
services:
api:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=test
- DATABASE_URL=postgresql://test:test@db:5432/ecommerce_test
- REDIS_URL=redis://cache:6379
- MAIL_HOST=mailhog
- MAIL_PORT=1025
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 5s
timeout: 5s
retries: 10
db:
image: postgres:15
environment:
POSTGRES_DB: ecommerce_test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
volumes:
- ./scripts/seed-test-data.sql:/docker-entrypoint-initdb.d/01-seed.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U test"]
interval: 5s
timeout: 5s
retries: 5
cache:
image: redis:7-alpine
mailhog:
image: mailhog/mailhog
ports:
- "8025:8025"
api-tests:
build:
context: .
dockerfile: Dockerfile.tests
environment:
- BASE_URL=http://api:3000
- MAILHOG_URL=http://mailhog:8025
command: npm run test:api
depends_on:
api:
condition: service_healthy
volumes:
- ./test-results/api:/app/test-results
e2e-tests:
image: mcr.microsoft.com/playwright:v1.40.0-focal
working_dir: /app
volumes:
- .:/app
- ./test-results/e2e:/app/test-results
environment:
- BASE_URL=http://api:3000
command: npx playwright test
depends_on:
api:
condition: service_healthy
Uso:
# Ejecutar tests de API
docker compose run --rm api-tests
# Ejecutar tests E2E
docker compose run --rm e2e-tests
# Limpiar
docker compose down -v
Mejores Prácticas
Siempre usa health checks con
depends_on: condition: service_healthy. El orden de inicio solo no es suficiente.Usa
docker compose down -vdespués de los tests. El flag-velimina volúmenes, asegurando que cada ejecución inicie con estado limpio.Monta resultados de tests como volúmenes. Esto hace los reportes accesibles en la máquina host y en artefactos CI.
Mantén datos de test en archivos SQL semilla montados vía
docker-entrypoint-initdb.d/. Esto asegura datos de test consistentes y reproducibles.Usa
docker compose run --rmpara ejecución de tests. El flag--rmelimina el contenedor después de que termine.Fija versiones de imágenes. Usa
postgres:15en lugar depostgres:latestpara evitar cambios de comportamiento inesperados.