Docker para Desarrolladores

Servicios, Redes y Volúmenes en Compose

CONCEPTO CLAVE: Docker Compose permite definir y ejecutar aplicaciones multi-contenedor. Cada servicio en tu archivo compose representa un contenedor, y Compose maneja automáticamente la creación de redes y gestión de volúmenes necesarios para que estos servicios se comuniquen y persistican datos.IntroducciónEn 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 r
Tiempo de estudio
25 Min
CONCEPTO CLAVE: Docker Compose permite definir y ejecutar aplicaciones multi-contenedor. Cada servicio en tu archivo compose representa un contenedor, y Compose maneja automáticamente la creación de redes y gestión de volúmenes necesarios para que estos servicios se comuniquen y persistican datos.

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"
💡 Tip: Usa siempre versiones específicas de imágenes en producción. En desarrollo puedes usar :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 yes
📌 Nota: depends_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: always

Las 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.env

Redes 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.

CONCEPTO CLAVE: Cada servicio en Docker Compose está disponible para otros servicios por su nombre de servicio. Si tienes un servicio llamado 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 externo
💡 Tip: Usar internal: 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
  • alias personalizados que puedes definir
services:
api:
image: node:18
networks:
app-network:
aliases:
- api-service
- rest-api

Volú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ónimo
⚠️ Advertencia: Los volúmenes anónimos no se persisten entre ejecuciones de docker-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 ejecuciones
📌 Nota: Los bind mounts son perfectos para desarrollo local porque permiten que los cambios en tu código se reflejen inmediatamente en el contenedor sin rebuilds.

Configuració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: true

Usos 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:
💡 Tip: El volumen 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.
Ver más: Comandos útiles de gestión

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 -v

Ejemplo 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:
📌 Arquitectura: Este compose define una red 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 config

Resumen de Comandos Esenciales

ComandoDescripción
docker-compose up -dIniciar todos los servicios en background
docker-compose downDetener y eliminar contenedores (mantiene volúmenes)
docker-compose down -vDetener y eliminar todo incluyendo volúmenes
docker-compose restartReiniciar todos los servicios
docker-compose buildRebuild imágenes de servicios
docker-compose up --buildBuild y ejecutar con un solo comando
docker-compose logs -f [service]Ver logs en tiempo real
docker-compose exec [service] shAbrir shell en servicio
🧠 Quiz: Servicios, Redes y Volúmenes en Compose

¿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
Respuesta correcta: A. Los volúmenes con nombre son gestionados por Docker y persistentes, mientras que los bind mounts mapean rutas específicas del sistema de archivos del host al contenedor.
🧠 Quiz

¿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
Respuesta correcta: B. depends_on establece una dependencia de inicio entre servicios, garantizando que el servicio dependiente arranque después del servicio especificado.
🧠 Quiz

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
Respuesta correcta: B. Una red con 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.
💡 Próximos pasos: En la siguiente lección aprenderemos sobre Desarrollo en Vivo con Volumes, profundizando en técnicas para hot-reload y sincronización de código entre tu entorno local y los contenedores.
Texto Leccion 2/15
Estas viendo
Servicios, Redes y Volúmenes en Compose
Hablar por WhatsAppContactar por WhatsApp