Si ya has usado Docker y has visto que una aplicación "de verdad" necesita más de un contenedor —una base de datos, una API, un frontend, una caché—, tarde o temprano te topas con el mismo problema: gestionar todo a base de docker run con sus parámetros se vuelve un caos. Docker Compose es la herramienta que convierte ese caos en un archivo YAML y un solo comando.

En este tutorial vas a aprender qué es Docker Compose, cómo escribir tu primer compose.yaml, cómo orquestar servicios que se comunican entre sí y cómo usar volúmenes y redes para que tus datos no desaparezcan al apagar los contenedores. Todo con ejemplos prácticos que puedes probar en tu propio equipo.


¿Qué es Docker Compose y por qué usarlo?

Docker Compose es una extensión de Docker que permite definir y ejecutar aplicaciones multi-contenedor desde un único archivo de configuración. En lugar de acordarte de los flags de cada docker run, escribes una vez la configuración en YAML y la levantas con un comando.

Piénsalo así: sin Compose, levantar una aplicación web con base de datos y caché implica ejecutar tres comandos manualmente, acordarte de crear una red para que se vean entre sí, montar volúmenes, exponer puertos. Con Compose, todo eso va en un archivo que puedes compartir, versionar y ejecutar en cualquier máquina.

Las ventajas prácticas son inmediatas:

  • Un solo comando para levantar todo el stack (docker compose up)
  • Configuración declarativa: todo lo que necesita tu aplicación está en un archivo
  • Aislamiento: cada servicio corre en su propio contenedor, como recomienda Docker
  • Reproducibilidad: el mismo archivo funciona en desarrollo, staging y producción
  • Escalabilidad: puedes escalar servicios concretos con docker compose up --scale

Requisitos para seguir este tutorial

Necesitas tener Docker instalado. Hoy en día hay dos caminos principales:

  • Docker Desktop (Windows, Mac, Linux): incluye Docker Engine, Docker CLI y Docker Compose en un solo instalador. Es la opción más cómoda para empezar.
  • Docker Engine + Compose plugin (Linux): instalas Docker Engine con tu gestor de paquetes y añades el plugin docker-compose por separado.

Verifica que todo está listo con estos comandos:

ice-tech
docker --version
docker compose version

Si ambos responden sin error, estás listo. No hace falta ser un experto en Docker, pero conviene tener claros los conceptos básicos: imágenes, contenedores y puertos.


Estructura de un archivo compose.yaml

El corazón de Docker Compose es el archivo compose.yaml (también acepta docker-compose.yml). Ahí describes los servicios que forman tu aplicación y cómo se relacionan.

La sección obligatoria es services. A partir de ahí puedes añadir:

Sección Descripción
services Define cada contenedor (API, base de datos, frontend...)
networks Redes personalizadas para la comunicación entre servicios
volumes Volúmenes para persistir datos
configs Archivos de configuración para servicios
secrets Gestión de contraseñas y claves API

Un compose.yaml mínimo sería algo así:

ice-tech
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"

Con esto, docker compose up -d descarga Nginx y lo arranca exponiendo el puerto 8080. Pero lo interesante de Compose empieza cuando tienes varios servicios.


Ejemplo práctico: aplicación web con Flask + Redis

Vamos a construir un ejemplo real: una aplicación web en Python con Flask que muestra un contador de visitas almacenado en Redis. Todo dentro de contenedores, sin instalar Python ni Redis en tu máquina.

Paso 1: estructura del proyecto

Crea un directorio para el proyecto y dentro coloca estos archivos:

app.py — la aplicación Flask:

ice-tech
import time
import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return f'Hola desde Docker Compose! Has visitado esta página {count} veces.'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

requirements.txt:

ice-tech
flask==3.1.0
redis==5.2.0

Dockerfile:

ice-tech
FROM python:3.12-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

Paso 2: el archivo compose.yaml

En la raíz del proyecto, crea compose.yaml:

ice-tech
services:
  web:
    build: .
    ports:
      - "8000:5000"
    depends_on:
      - redis

  redis:
    image: redis:7-alpine

Fíjate en depends_on: le dice a Compose que el servicio web necesita que redis esté disponible antes de arrancar. No es una garantía de salud (Redis puede estar arrancando aún), pero sí ordena la secuencia de inicio.

Paso 3: levantar la aplicación

Ejecuta en el directorio del proyecto:

ice-tech
docker compose up -d

Docker Compose construye la imagen de tu aplicación (usando el Dockerfile), descarga la imagen de Redis, crea los contenedores y los arranca en segundo plano. Abre http://localhost:8000 y verás el contador. Cada vez que recargues, se incrementa.

Para ver el estado:

ice-tech
docker compose ps

Para ver logs en tiempo real:

ice-tech
docker compose logs -f

Para parar y limpiar todo:

ice-tech
docker compose down

Mejorando el desarrollo con bind mounts

En el ejemplo anterior, cada vez que modificas app.py tienes que reconstruir la imagen y reiniciar el contenedor. En desarrollo eso es tedioso. La solución son los bind mounts: montar tu directorio local dentro del contenedor para que los cambios se reflejen al instante.

Modifica el servicio web en compose.yaml:

ice-tech
services:
  web:
    build: .
    ports:
      - "8000:5000"
    volumes:
      - .:/app
    depends_on:
      - redis

Ahora cualquier cambio que hagas en app.py se ve reflejado dentro del contenedor. Si además arrancas Flask en modo debug (FLASK_DEBUG=1 como variable de entorno), el servidor se recarga automáticamente al guardar el archivo.

Docker Compose Watch va un paso más allá: con docker compose up --watch, Compose vigila los archivos del proyecto y sincroniza los cambios sin necesidad de bind mounts explícitos. Cuando guardas un archivo, lo copia al contenedor y reinicia el proceso si es necesario.


Redes y volúmenes

Redes

Por defecto, Compose crea una red para tu proyecto y todos los servicios se conectan a ella. Pueden comunicarse usando el nombre del servicio como hostname. Por eso en app.py usamos redis como host: Docker resuelve ese nombre automáticamente al contenedor de Redis.

Puedes definir redes personalizadas para tener más control:

ice-tech
services:
  web:
    build: .
    networks:
      - frontend
      - backend

  redis:
    image: redis:7-alpine
    networks:
      - backend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

Aquí web está en dos redes, pero redis solo en backend. Si añadieras una base de datos, podría estar solo en backend y no exponerse a frontend. Esto reduce la superficie de ataque y mantiene los servicios separados por función.

Volúmenes

Los contenedores son efímeros: cuando los eliminas, todo lo que hay dentro desaparece. Para los datos que deben persistir —bases de datos, archivos subidos, configuraciones— usamos volúmenes.

Un ejemplo con MongoDB:

ice-tech
services:
  database:
    image: mongo:7
    volumes:
      - mongodb_data:/data/db

volumes:
  mongodb_data:

El volumen mongodb_data se define en la sección volumes y se monta en /data/db, que es donde Mongo guarda sus datos. Aunque elimines y recrees el contenedor, los datos sobreviven en el volumen gestionado por Docker.


Comandos esenciales de Docker Compose

En el día a día, con unos pocos comandos tienes cubierto casi todo el flujo de trabajo:

Comando Qué hace
docker compose up -d Construye imágenes (si es necesario) y levanta todos los servicios en segundo plano
docker compose down Detiene y elimina contenedores, redes y (opcionalmente) volúmenes
docker compose ps Muestra el estado de los servicios
docker compose logs -f Sigue los logs de todos los servicios en tiempo real
docker compose logs -f web Logs de un servicio concreto
docker compose build Reconstruye las imágenes sin arrancar los servicios
docker compose exec web sh Abre una shell dentro del contenedor web
docker compose restart Reinicia todos los servicios

Un tip: si ves que un servicio no arranca bien, prueba docker compose logs -f <servicio> para ver exactamente qué está fallando. Nueve de cada diez veces es un error de configuración o de conexión entre servicios.


Errores comunes (y cómo evitarlos)

Olvidar el .dockerignore: sin este archivo, COPY . . incluye node_modules, .env y otros archivos que no deberían estar en la imagen. Crea siempre un .dockerignore:

ice-tech
node_modules
.git
.env
__pycache__
*.log

Dependencias sin orden real: depends_on asegura el orden de arranque, pero no espera a que el servicio esté listo. Redis puede arrancar en 1 segundo, pero una base de datos puede tardar 20. En producción usa herramientas como healthcheck o scripts de espera.

Puertos en conflicto: si dos stacks usan el mismo puerto del host, el segundo falla. Planifica los puertos o usa rangos dinámicos.

Olvidar los volúmenes: es el error más común con bases de datos. Arrancas un contenedor de PostgreSQL, creas tablas, haces docker compose down, y al levantar otra vez... todo ha desaparecido. Solución: define volúmenes para cualquier dato que quieras conservar.

Usar latest sin control: las etiquetas latest cambian con el tiempo y pueden romper tu stack. Prefiere versiones concretas: redis:7-alpine en lugar de redis:latest.


Docker Compose es una de esas herramientas que, una vez que las usas, te preguntas cómo trabajabas sin ella. Te permite definir entornos completos en un archivo YAML, compartirlos con tu equipo, y pasar de desarrollo a producción con la misma configuración. Si ya usas Docker, es el siguiente paso natural. Si no, este tutorial es una buena excusa para empezar.

Ahora toca ponerlo en práctica. Crea un compose.yaml para tu próximo proyecto, aunque sea pequeño, y verás cómo cambia la forma en que trabajas con contenedores.