Introducción
En esta lección profundizaremos en los tres pilares fundamentales de Docker Compose: servicios, redes y volúmenes. Dominar estos conceptos te permitirá crear entornos de desarrollo locales robustos, reproducibles y idénticos entre todos los miembros de tu equipo.
Cuando trabajas con aplicaciones reales, rara vez tienes un solo contenedor. Típicamente necesitas una base de datos, un servidor web, un servicio de caché, quizás un message broker. Docker Compose resuelve este problema permitiéndote definir toda esta arquitectura en un único archivo docker-compose.yml.
Definición de Servicios
Los servicios son la pieza central de Docker Compose. Cada servicio representa un contenedor que se creará a partir de una imagen específica. Veamos la estructura básica:
version: '3.8'
services:
web:
image: nginx:alpine
ports:
- "80:80"
environment:
- NODE_ENV=development
volumes:
- ./app:/usr/share/nginx/html
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: developer
POSTGRES_PASSWORD: devpassword
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432":latest, pero especifica la versión exacta para evitar sorpresas cuando se actualicen las imágenes.Configuración avanzada de servicios
Los servicios ofrecen numerosas opciones de configuración. Veamos las más relevantes para desarrollo local:
Dependencias entre servicios
Usa la opción depends_on para asegurar que un servicio inicie después de otro:
services:
backend:
build: ./backend
depends_on:
- db
- redis
db:
image: postgres:15
redis:
image: redis:7-alpine
command: redis-server --appendonly yesdepends_on solo garantiza que el contenedor inicie en orden, no que el servicio esté completamente listo. Para aplicaciones que necesitan esperar a que una base de datos acepte conexiones, considera usar scripts de wait o herramientas como wait-for-it.sh o dockerize.Control de restart
Configura el comportamiento de reinicio del contenedor:
services:
api:
image: node:18
restart: unless-stopped
worker:
image: node:18
restart: on-failure
monitor:
image: prometheus
restart: alwaysLas opciones disponibles son: no (nunca reiniciar), always (siempre reiniciar), on-failure (reiniciar solo si sale con código de error), y unless-stopped (reiniciar siempre excepto si se detuvo manualmente).
Variables de entorno
Docker Compose soporta múltiples formas de definir variables de entorno:
services:
api:
image: node:18
environment:
# Forma explícita
DB_HOST: db
DB_PORT: 5432
# Forma corta (valor desde el host)
- NODE_ENV
# Archivo .env
env_file:
- .env.production
- ./config/secrets.envRedes en Docker Compose
Por defecto, Docker Compose crea una red para todos los servicios definidos, permitiéndoles comunicarse entre sí usando los nombres de servicio como hostnames. Sin embargo, puedes customize esta configuración para casos más complejos.
database, los demás servicios pueden acceder a él usando database como hostname.Redes personalizadas
Para arquitecturas más complejas, puedes definir múltiples redes:
version: '3.8'
services:
frontend:
image: nginx:alpine
networks:
- web-network
ports:
- "80:80"
backend:
image: node:18
networks:
- web-network
- api-network
depends_on:
- db
db:
image: postgres:15
networks:
- api-network
volumes:
- db_data:/var/lib/postgresql/data
networks:
web-network:
driver: bridge
api-network:
driver: bridge
internal: true # Redisolada, sin acceso externointernal: true en una red es útil para bases de datos o servicios internos que no necesitan exposición externa, añadiendo una capa extra de seguridad.DNS y resolución de nombres
Docker Compose configura automáticamente un DNS interno. Cada servicio puede resolver:
- Su propio nombre de servicio
- Otros nombres de servicio en la misma red
aliaspersonalizados que puedes definir
services:
api:
image: node:18
networks:
app-network:
aliases:
- api-service
- rest-apiVolúmenes y Persistencia de Datos
Los volúmenes son esenciales para persistir datos más allá del ciclo de vida de un contenedor. Sin volúmenes, cualquier dato guardado dentro del contenedor se pierde cuando se elimina.
Tipos de volúmenes
Docker Compose soporta tres tipos de volúmenes:
1. Volúmenes anónimos (anonymous volumes)
services:
app:
image: node:18
volumes:
- /data # Docker crea un volumen anónimodocker-compose down. Son útiles para datos temporales o cachés que no necesitas mantener.2. Volúmenes con nombre (named volumes)
services:
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:Los volúmenes con nombre persisten hasta que se eliminen explícitamente con docker volume rm. Son ideales para bases de datos y cualquier dato que necesites mantener.
3. Bind mounts (montajes de enlace)
services:
frontend:
image: node:18
volumes:
- ./src:/app/src:ro # :ro = solo lectura
- ./package.json:/app/package.json
api:
image: node:18
volumes:
- ~/.npm:/root/.npm # Cache de npm entre ejecucionesConfiguración avanzada de volúmenes
volumes:
db_data:
driver: local
driver_opts:
type: none
o: bind
device: /host/path
s3_backup:
driver: local-persist
driver_opts:
persistonchange: trueUsos prácticos de volúmenes
Veamos un ejemplo completo de una aplicación con persistencia adecuada:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
volumes:
- ./src:/app/src # Código fuente en vivo
- node_modules:/app/node_modules # Preserva node_modules
environment:
- NODE_ENV=development
- DB_HOST=postgres
depends_on:
- postgres
- redis
command: npm run dev
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: myapp_dev
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev123
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
postgres_data:
redis_data:
node_modules:node_modules:/app/node_modules es un patrón muy útil. Vincula un volumen con nombre sobre el directorio local (que está vacío o tiene dependencias diferentes) para preservar los node_modules del contenedor y evitar problemas de compatibilidad entre sistemas operativos.Comandos esenciales para trabajar con volúmenes en desarrollo:
# Listar volúmenes
docker volume ls
# Inspeccionar un volumen
docker volume inspect mi_proyecto_postgres_data
# Eliminar volúmenes huérfanos
docker volume prune
# Eliminar un volumen específico
docker volume rm mi_proyecto_postgres_data
# Ver uso de disco
docker system df -vEjemplo Integrado: Stack MEAN Completo
Veamos un ejemplo completo que integra todos los conceptos:
version: '3.8'
services:
mongodb:
image: mongo:6
restart: unless-stopped
networks:
- backend
volumes:
- mongodb_data:/data/db
environment:
MONGO_INITDB_DATABASE: myapp
ports:
- "27017:27017"
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
restart: unless-stopped
networks:
- backend
volumes:
- ./backend/src:/app/src
- backend_modules:/app/node_modules
environment:
NODE_ENV: development
MONGODB_URI: mongodb://mongodb:27017/myapp
JWT_SECRET: dev-secret-change-in-prod
ports:
- "5000:5000"
depends_on:
- mongodb
command: npm run dev
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
restart: unless-stopped
networks:
- frontend
- backend
volumes:
- ./frontend/src:/app/src
- frontend_modules:/app/node_modules
environment:
REACT_APP_API_URL: http://localhost:5000
ports:
- "3000:3000"
depends_on:
- backend
command: npm start
nginx:
image: nginx:alpine
restart: unless-stopped
networks:
- frontend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
ports:
- "80:80"
- "443:443"
depends_on:
- backend
- frontend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # La base de datos solo es accesible internamente
volumes:
mongodb_data:
backend_modules:
frontend_modules:backend interna donde solo el backend puede comunicarse con MongoDB. El frontend accede al backend a través de Nginx (reverse proxy). Esta separación añade seguridad y claridad a la arquitectura.Debugging y Troubleshooting
Cuando algo no funciona, estos comandos te ayudarán a diagnosticar problemas:
# Ver logs de todos los servicios
docker-compose logs -f
# Ver logs de un servicio específico
docker-compose logs -f backend
# Ver logs de múltiples servicios
docker-compose logs -f backend db redis
# Ejecutar un comando en un servicio existente
docker-compose exec backend npm run debug
# Abrir una shell en un contenedor
docker-compose exec backend sh
# Ver estado de todos los servicios
docker-compose ps
# Ver configuración resuelta
docker-compose configResumen de Comandos Esenciales
| Comando | Descripción |
|---|---|
docker-compose up -d | Iniciar todos los servicios en background |
docker-compose down | Detener y eliminar contenedores (mantiene volúmenes) |
docker-compose down -v | Detener y eliminar todo incluyendo volúmenes |
docker-compose restart | Reiniciar todos los servicios |
docker-compose build | Rebuild imágenes de servicios |
docker-compose up --build | Build y ejecutar con un solo comando |
docker-compose logs -f [service] | Ver logs en tiempo real |
docker-compose exec [service] sh | Abrir shell en servicio |
¿Cuál es la diferencia principal entre un volumen con nombre y un bind mount?
- A) Los volúmenes con nombre se crean y gestionan por Docker, mientras que los bind mounts mapean un directorio específico del host
- B) No hay diferencia, son exactamente iguales
- C) Los bind mounts solo funcionan en Windows
- D) Los volúmenes con nombre solo funcionan en producción
¿Para qué sirve la opción depends_on en un servicio?
- A) Copiar archivos de un contenedor a otro
- B) Asegurar que un servicio inicie después de otro
- C) Compartir variables de entorno entre servicios
- D) Crear una red privada entre servicios
depends_on establece una dependencia de inicio entre servicios, garantizando que el servicio dependiente arranque después del servicio especificado.Si quieres que tu base de datos NO sea accesible desde el exterior y solo se comunique con tu backend, ¿qué configuración de red usarías?
- A) No definir redes (usar default)
- B) Crear una red con
internal: true - C) Usar el driver host
- D) Asignar la base de datos a dos redes públicas
internal: true impide cualquier conexión externa, aislando los servicios dentro de esa red.Dominar servicios, redes y volúmenes en Docker Compose es fundamental para crear entornos de desarrollo reproducibles. Practica con ejemplos reales y experimentarás cómo Docker Compose simplifica enormemente la gestión de aplicaciones complejas.