Cristhian Villegas
DevOps12 min read0 views

Curso Docker #6: Docker Compose — Aplicaciones Multi-Contenedor

Curso Docker #6: Docker Compose — Aplicaciones Multi-Contenedor

Bienvenido al Curso de Docker - Parte 6 de 10. Hasta ahora hemos trabajado con contenedores individuales, pero las aplicaciones reales suelen necesitar multiples servicios trabajando juntos: una base de datos, un servidor web, un cache, etc. Gestionar todo esto manualmente con docker run se vuelve tedioso y propenso a errores.

Logo de Docker

Fuente: Wikimedia Commons

En este articulo aprenderemos Docker Compose, la herramienta oficial de Docker para definir y ejecutar aplicaciones multi-contenedor con un solo archivo de configuracion.

Que es Docker Compose y por que lo necesitas

Docker Compose es una herramienta que permite definir aplicaciones multi-contenedor usando un archivo YAML llamado docker-compose.yml (o compose.yml). En lugar de escribir largos comandos docker run con decenas de flags, defines toda la infraestructura en un archivo declarativo.

Las ventajas principales son:

  • Reproducibilidad: cualquier persona puede levantar el mismo entorno con un solo comando
  • Versionado: el archivo se guarda en el repositorio junto con el codigo
  • Simplicidad: un comando docker compose up reemplaza decenas de comandos individuales
  • Aislamiento: cada proyecto tiene su propia red y volumenes independientes
Nota: Desde Docker Desktop 3.4+, docker compose (sin guion) es el comando integrado. El antiguo docker-compose (con guion) es la version legacy en Python. En este articulo usaremos la version moderna docker compose.

Estructura del archivo docker-compose.yml

Un archivo Compose tiene una estructura clara con secciones principales:

yaml
1# docker-compose.yml
2services:
3  # Definicion de cada contenedor
4  web:
5    image: nginx:alpine
6    ports:
7      - "8080:80"
8
9  api:
10    build: ./backend
11    ports:
12      - "3000:3000"
13    environment:
14      - NODE_ENV=production
15
16  db:
17    image: postgres:16
18    volumes:
19      - db_data:/var/lib/postgresql/data
20    environment:
21      POSTGRES_PASSWORD: secreto123
22
23# Volumenes persistentes
24volumes:
25  db_data:
26
27# Redes personalizadas (opcional)
28networks:
29  frontend:
30  backend:

Las secciones principales son:

Seccion Descripcion
services Define cada contenedor de la aplicacion
volumes Volumenes nombrados para persistir datos
networks Redes personalizadas para comunicacion entre servicios

Servicios: configuracion detallada

Cada servicio dentro de services puede usar una imagen existente o construir una desde un Dockerfile. Veamos las opciones mas comunes:

yaml
1services:
2  mi-servicio:
3    # Opcion 1: usar imagen existente
4    image: node:20-alpine
5
6    # Opcion 2: construir desde Dockerfile
7    build:
8      context: ./mi-app
9      dockerfile: Dockerfile.prod
10
11    # Puertos expuestos (host:contenedor)
12    ports:
13      - "3000:3000"
14
15    # Variables de entorno
16    environment:
17      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
18      - NODE_ENV=production
19
20    # O desde un archivo .env
21    env_file:
22      - .env
23
24    # Volumenes
25    volumes:
26      - ./src:/app/src          # bind mount para desarrollo
27      - node_modules:/app/node_modules  # volumen nombrado
28
29    # Redes
30    networks:
31      - backend
32
33    # Comando personalizado
34    command: npm run start:dev
35
36    # Reinicio automatico
37    restart: unless-stopped
38
39    # Limites de recursos
40    deploy:
41      resources:
42        limits:
43          memory: 512M
44          cpus: "0.5"
Tip: Dentro de una red de Compose, los servicios se comunican usando el nombre del servicio como hostname. Si tu base de datos se llama db, tu app se conecta a db:5432 en lugar de localhost:5432.

Redes y volumenes en Compose

Docker Compose crea automaticamente una red por defecto para todos los servicios del proyecto. Sin embargo, puedes crear redes personalizadas para aislar grupos de servicios:

yaml
1services:
2  frontend:
3    image: nginx:alpine
4    networks:
5      - red-publica
6
7  api:
8    build: ./api
9    networks:
10      - red-publica
11      - red-interna
12
13  db:
14    image: postgres:16
15    networks:
16      - red-interna
17    volumes:
18      - datos_postgres:/var/lib/postgresql/data
19
20  redis:
21    image: redis:7-alpine
22    networks:
23      - red-interna
24    volumes:
25      - datos_redis:/data
26
27networks:
28  red-publica:
29    driver: bridge
30  red-interna:
31    driver: bridge
32    internal: true  # sin acceso a internet
33
34volumes:
35  datos_postgres:
36  datos_redis:

En este ejemplo, el servicio db y redis solo son accesibles desde api, nunca desde frontend. La red red-interna con internal: true ademas bloquea el acceso a internet desde esos contenedores.

Variables de entorno y archivos .env

Docker Compose soporta multiples formas de inyectar variables de entorno:

yaml
1# docker-compose.yml
2services:
3  api:
4    image: mi-api:latest
5    # Forma 1: inline
6    environment:
7      - DB_HOST=db
8      - DB_PORT=5432
9      - DB_NAME=${POSTGRES_DB}  # interpolacion desde el shell o .env
10
11    # Forma 2: archivo externo
12    env_file:
13      - .env
14      - .env.local  # sobreescribe valores de .env

El archivo .env se carga automaticamente si esta en el mismo directorio que docker-compose.yml:

bash
1# .env
2POSTGRES_DB=mi_aplicacion
3POSTGRES_USER=admin
4POSTGRES_PASSWORD=SuperSecreto123
5API_PORT=3000
6NODE_ENV=production
Importante: Nunca subas el archivo .env al repositorio. Agrega .env a tu .gitignore y proporciona un archivo .env.example con las variables necesarias pero sin valores sensibles.

depends_on y healthchecks

El orden de inicio es crucial. Si tu API se levanta antes que la base de datos, fallara al intentar conectarse. Docker Compose ofrece depends_on para controlar dependencias:

yaml
1services:
2  db:
3    image: postgres:16
4    environment:
5      POSTGRES_PASSWORD: secreto
6    healthcheck:
7      test: ["CMD-SHELL", "pg_isready -U postgres"]
8      interval: 5s
9      timeout: 5s
10      retries: 5
11      start_period: 10s
12
13  redis:
14    image: redis:7-alpine
15    healthcheck:
16      test: ["CMD", "redis-cli", "ping"]
17      interval: 5s
18      timeout: 3s
19      retries: 5
20
21  api:
22    build: ./api
23    depends_on:
24      db:
25        condition: service_healthy
26      redis:
27        condition: service_healthy
28    environment:
29      DATABASE_URL: postgresql://postgres:secreto@db:5432/postgres
30      REDIS_URL: redis://redis:6379

Sin la opcion condition: service_healthy, Docker solo espera a que el contenedor inicie, no a que el servicio dentro del contenedor este listo. Con healthchecks, Compose espera hasta que la base de datos realmente acepte conexiones.

Error comun: Usar depends_on sin condition: service_healthy no garantiza que el servicio dependiente este listo. Solo garantiza que el contenedor se inicio. Siempre configura healthchecks para bases de datos y servicios criticos.

Comandos esenciales de Docker Compose

Estos son los comandos que usaras a diario:

bash
1# Levantar todos los servicios (modo detached)
2docker compose up -d
3
4# Levantar y forzar rebuild de imagenes
5docker compose up -d --build
6
7# Ver logs de todos los servicios
8docker compose logs
9
10# Ver logs de un servicio especifico en tiempo real
11docker compose logs -f api
12
13# Ver el estado de los servicios
14docker compose ps
15
16# Detener todos los servicios
17docker compose down
18
19# Detener y eliminar volumenes (CUIDADO: borra datos)
20docker compose down -v
21
22# Ejecutar un comando en un servicio en ejecucion
23docker compose exec api sh
24
25# Escalar un servicio (crear multiples instancias)
26docker compose up -d --scale api=3
27
28# Ver la configuracion final (util para debug)
29docker compose config
30
31# Reiniciar un servicio especifico
32docker compose restart api
33
34# Pull de las imagenes mas recientes
35docker compose pull
Comando Descripcion
up -d Levanta servicios en segundo plano
down Detiene y elimina contenedores y redes
logs -f Muestra logs en tiempo real
ps Lista servicios y su estado
exec Ejecuta un comando dentro de un servicio
down -v Detiene y elimina todo, incluyendo volumenes

Proyecto practico: WordPress + MySQL + phpMyAdmin

Vamos a crear un stack completo de WordPress con su base de datos y un panel de administracion para la base de datos. Este es un ejemplo real que puedes usar para desarrollo local.

Crea un directorio para el proyecto y el archivo docker-compose.yml:

bash
1mkdir wordpress-stack && cd wordpress-stack
yaml
1# docker-compose.yml
2services:
3  # Base de datos MySQL
4  mysql:
5    image: mysql:8.0
6    container_name: wp-mysql
7    restart: unless-stopped
8    environment:
9      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
10      MYSQL_DATABASE: ${MYSQL_DATABASE}
11      MYSQL_USER: ${MYSQL_USER}
12      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
13    volumes:
14      - mysql_data:/var/lib/mysql
15    networks:
16      - wp-network
17    healthcheck:
18      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
19      interval: 10s
20      timeout: 5s
21      retries: 5
22      start_period: 30s
23
24  # WordPress
25  wordpress:
26    image: wordpress:6-apache
27    container_name: wp-app
28    restart: unless-stopped
29    depends_on:
30      mysql:
31        condition: service_healthy
32    ports:
33      - "8080:80"
34    environment:
35      WORDPRESS_DB_HOST: mysql:3306
36      WORDPRESS_DB_USER: ${MYSQL_USER}
37      WORDPRESS_DB_PASSWORD: ${MYSQL_PASSWORD}
38      WORDPRESS_DB_NAME: ${MYSQL_DATABASE}
39      WORDPRESS_TABLE_PREFIX: wp_
40    volumes:
41      - wp_html:/var/www/html
42      - ./wp-content/themes:/var/www/html/wp-content/themes
43      - ./wp-content/plugins:/var/www/html/wp-content/plugins
44    networks:
45      - wp-network
46
47  # phpMyAdmin para administrar la base de datos
48  phpmyadmin:
49    image: phpmyadmin:5
50    container_name: wp-phpmyadmin
51    restart: unless-stopped
52    depends_on:
53      mysql:
54        condition: service_healthy
55    ports:
56      - "8081:80"
57    environment:
58      PMA_HOST: mysql
59      PMA_PORT: 3306
60      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
61    networks:
62      - wp-network
63
64# Volumenes persistentes
65volumes:
66  mysql_data:
67    name: wp-mysql-data
68  wp_html:
69    name: wp-html-data
70
71# Red del proyecto
72networks:
73  wp-network:
74    name: wordpress-network
75    driver: bridge

Ahora crea el archivo de variables de entorno:

bash
1# .env
2MYSQL_ROOT_PASSWORD=R00tP@ssw0rd2026
3MYSQL_DATABASE=wordpress_db
4MYSQL_USER=wp_user
5MYSQL_PASSWORD=WpP@ssw0rd2026

Levanta el stack completo:

bash
1# Levantar todo
2docker compose up -d
3
4# Verificar que todo esta corriendo
5docker compose ps
6
7# Ver los logs de MySQL hasta que este listo
8docker compose logs -f mysql
9
10# Acceder a WordPress: http://localhost:8080
11# Acceder a phpMyAdmin: http://localhost:8081
Tip: Los bind mounts de themes y plugins permiten desarrollar temas y plugins directamente en tu maquina local. Los cambios se reflejan inmediatamente en WordPress sin necesidad de reconstruir el contenedor.

Buenas practicas con Docker Compose

Para mantener tus archivos Compose limpios y profesionales, sigue estas recomendaciones:

  • Nombra tus contenedores con container_name para identificarlos facilmente en los logs
  • Usa .env para secretos y nunca hardcodees contrasenas en el YAML
  • Configura healthchecks para todas las bases de datos y servicios criticos
  • Usa restart: unless-stopped para que los servicios se recuperen de caidas
  • Nombra volumenes y redes con name: para evitar prefijos automaticos
  • Separa redes para que los servicios solo se comuniquen con quienes necesiten
  • Usa perfiles (profiles) para herramientas opcionales como phpMyAdmin o debug tools
yaml
1# Ejemplo de perfiles: phpMyAdmin solo se levanta con --profile debug
2services:
3  phpmyadmin:
4    image: phpmyadmin:5
5    profiles:
6      - debug
7    ports:
8      - "8081:80"
9
10# Levantar sin phpMyAdmin:
11#   docker compose up -d
12# Levantar con phpMyAdmin:
13#   docker compose --profile debug up -d

Resumen y siguiente articulo

En este articulo aprendimos todo lo necesario para trabajar con Docker Compose:

  • Que es Docker Compose y por que simplifica la gestion de aplicaciones multi-contenedor
  • Estructura del archivo docker-compose.yml: services, volumes, networks
  • Configuracion de servicios: imagenes, build, puertos, variables de entorno
  • Redes y volumenes para comunicacion y persistencia de datos
  • depends_on con healthchecks para control de orden de inicio
  • Comandos esenciales: up, down, logs, ps, exec
  • Proyecto practico: stack completo de WordPress + MySQL + phpMyAdmin
Siguiente articulo: En la parte 7 aprenderemos sobre Multi-Stage Builds — como optimizar tus imagenes Docker para reducir drasticamente su tamano con ejemplos para Java, Node.js y Python. Referencia oficial: Docker Compose Documentation.
Share:
CV

Cristhian Villegas

Software Engineer specializing in Java, Spring Boot, Angular & AWS. Building scalable distributed systems with clean architecture.

Comments

Sign in to leave a comment

No comments yet. Be the first!

Related Articles