# Inicio rápido de Docker Compose


Este tutorial tiene como objetivo introducir los conceptos fundamentales de Docker Compose guiándote en el desarrollo de una aplicación web básica en Python.

Utilizando el framework Flask, la aplicación cuenta con un contador de visitas en Redis, lo que proporciona un ejemplo práctico de cómo se puede aplicar Docker Compose en escenarios de desarrollo web. Los conceptos que se muestran aquí deberían ser comprensibles incluso si no estás familiarizado con Python.

## Requisitos previos

Asegúrate de tener:

- [Instalada la última versión de Docker Compose](/compose/install/)
- Una comprensión básica de los conceptos de Docker y de cómo funciona Docker

## Paso 1: Configurar el proyecto

1. Crea un directorio para el proyecto:

   ```console
   $ mkdir compose-demo
   $ cd compose-demo
   ```

2. Crea el archivo `app.py` en el directorio de tu proyecto y añade lo siguiente:

   ```python
   import os
   import redis
   from flask import Flask

   app = Flask(__name__)
   cache = redis.Redis(
       host=os.getenv("REDIS_HOST", "redis"),
       port=int(os.getenv("REDIS_PORT", "6379")),
   )

   @app.route("/")
   def hello():
       count = cache.incr("hits")
       return f"Hello from Docker! I have been seen {count} time(s).\n"
   ```

   La aplicación lee los detalles de su conexión a Redis desde variables de entorno, con valores predeterminados razonables para que funcione de inmediato.

3. Crea el archivo `requirements.txt` en el directorio de tu proyecto y añade lo siguiente:

   ```text
   flask
   redis
   ```

4. Crea un `Dockerfile`:

   ```dockerfile
   # syntax=docker/dockerfile:1
   FROM python:3.12-alpine  # Construye una imagen a partir de la imagen de Python 3.12
   WORKDIR /code  # Establece el directorio de trabajo en `/code`
   ENV FLASK_APP=app.py  # Establece las variables de entorno utilizadas por el comando `flask`
   ENV FLASK_RUN_HOST=0.0.0.0
   RUN apk add --no-cache gcc musl-dev linux-headers  # Instala `gcc` y otras dependencias
   COPY requirements.txt .  # Copia `requirements.txt`
   RUN pip install -r requirements.txt  # Instala las dependencias de Python
   COPY . .  # Copia el directorio actual `.` del proyecto en el directorio de trabajo `.` de la imagen
   EXPOSE 5000
   CMD ["flask", "run", "--debug"]  # Establece el comando predeterminado para el contenedor a `flask run --debug`
   ```

   > [!IMPORTANT]
   >
   > Asegúrate de que el archivo se llame `Dockerfile` sin extensión. Algunos editores añaden `.txt` automáticamente, lo que hace que la construcción falle.

   Para obtener más información sobre cómo escribir Dockerfiles, consulta la [referencia de Dockerfile](/reference/dockerfile/).

5. Crea un archivo `.env` para almacenar los valores de configuración:

   ```text
   APP_PORT=8000
   REDIS_HOST=redis
   REDIS_PORT=6379
   ```

   Compose lee automáticamente el archivo `.env` y hace que estos valores estén disponibles para su interpolación en tu archivo `compose.yaml`. Para este ejemplo, los beneficios son modestos, pero en la práctica, mantener la configuración fuera del archivo de Compose facilita:
   - Cambiar valores entre entornos sin tener que editar el archivo YAML
   - Evitar subir secretos al sistema de control de versiones
   - Reutilizar valores en múltiples servicios

6. Crea un archivo `.dockerignore` para mantener los archivos innecesarios fuera del contexto de construcción:

   ```text
   .env
   *.pyc
   __pycache__
   redis-data
   ```

   Docker envía todo lo que se encuentra en el directorio de tu proyecto al demonio cuando construye una imagen. Sin `.dockerignore`, eso incluye tu archivo `.env` (que puede contener secretos) y cualquier código de bytes de Python en caché. Excluirlos mantiene las construcciones rápidas y evita integrar accidentalmente valores sensibles en una capa de la imagen.

## Paso 2: Definir e iniciar tus servicios

Compose simplifica el control de todo tu stack de aplicaciones, facilitando la gestión de servicios, redes y volúmenes en un solo archivo de configuración YAML.

1. Crea el archivo `compose.yaml` en el directorio de tu proyecto y pega lo siguiente:

   ```yaml
   services:
     web:
       build: .
       ports:
         - "${APP_PORT}:5000"
       environment:
         - REDIS_HOST=${REDIS_HOST}
         - REDIS_PORT=${REDIS_PORT}

     redis:
       image: redis:alpine
   ```

   Este archivo de Compose define dos servicios:
   - El servicio `web` utiliza una imagen que se construye a partir del `Dockerfile` en el directorio actual. Mapea el puerto `8000` del host al puerto `5000` del contenedor donde Flask escucha de forma predeterminada.

   - El servicio `redis` utiliza una imagen pública de [Redis](https://registry.hub.docker.com/_/redis/) descargada del registro Docker Hub.

   Para obtener más información sobre el archivo `compose.yaml`, consulta [Cómo funciona Compose](/compose/gettingstarted/compose-application-model/).

2. Inicia tu aplicación:

   ```console
   $ docker compose up
   ```

   Con un solo comando, creas e inicias todos los servicios a partir de tu archivo de configuración. Compose construye tu imagen web, descarga la imagen de Redis e inicia ambos contenedores.

3. Abre `http://localhost:8000`. Deberías ver:

   ```text
   Hello from Docker! I have been seen 1 time(s).
   ```

   Actualiza la página: el contador se incrementa con cada visita.

   Esta configuración mínima funciona, pero presenta dos problemas que solucionarás en los siguientes pasos:
   - Carrera de inicio (startup race): `web` se inicia al mismo tiempo que `redis`. Si Redis aún no está listo, la aplicación Flask falla al conectarse y se bloquea.
   - Sin persistencia: Si ejecutas `docker compose down` seguido de `docker compose up`, el contador se restablece a cero. `docker compose down` elimina los contenedores y, con ellos, cualquier dato escrito en la capa de escritura del contenedor. `docker compose stop` conserva los contenedores para que los datos sobrevivan, pero no puedes confiar en eso en producción, donde los contenedores se reemplazan con regularidad.

4. Detén el stack antes de continuar:

   ```console
   $ docker compose down
   ```

## Paso 3: Solucionar la carrera de inicio con comprobaciones de estado

Para solucionar la carrera de inicio, Compose debe esperar hasta que se confirme que `redis` está en buen estado (healthy) antes de iniciar `web`.

1. Actualiza `compose.yaml`:

   ```yaml
   services:
     web:
       build: .
       ports:
         - "${APP_PORT}:5000"
       environment:
         - REDIS_HOST=${REDIS_HOST}
         - REDIS_PORT=${REDIS_PORT}
       depends_on:
         redis:
           condition: service_healthy

     redis:
       image: redis:alpine
       healthcheck:
         test: ["CMD", "redis-cli", "ping"]
         interval: 5s
         timeout: 3s
         retries: 5
         start_period: 10s
   ```

   El bloque `healthcheck` le indica a Compose cómo comprobar si Redis está listo:
   - `test` es el comando que Compose ejecuta dentro del contenedor para verificar su estado. `redis-cli ping` se conecta a Redis y espera una respuesta `PONG`: si la obtiene, el contenedor está en buen estado.
   - `start_period` le otorga a Redis 10 segundos para inicializarse antes de que comiencen las comprobaciones de estado. Cualquier fallo durante este intervalo no cuenta para el límite de reintentos.
   - `interval` ejecuta la comprobación cada 5 segundos una vez transcurrido el periodo de inicio.
   - `timeout` otorga a cada comprobación 3 segundos para responder antes de considerarla un fallo.
   - `retries` establece cuántos fallos consecutivos se permiten antes de que Compose marque el contenedor como no saludable. Con `interval: 5s` y `retries: 5`, Compose esperará hasta 25 segundos antes de rendirse.

2. Inicia el stack para confirmar que el orden se ha solucionado:

   ```console
   $ docker compose up
   ```

   Deberías ver algo similar a:

   ```text
   [+] Running 2/2
   ✔ Container compose-demo-redis-1  Healthy                       0.0s
   ```

3. Abre `http://localhost:8000` para confirmar que la aplicación sigue funcionando, luego detén el stack antes de continuar:

   ```console
   $ docker compose down
   ```

## Paso 4: Habilitar Compose Watch para actualizaciones en vivo

Sin Compose Watch, cada cambio de código requiere detener el stack, reconstruir la imagen y reiniciar los contenedores. Compose Watch elimina ese ciclo al sincronizar automáticamente los cambios en tu contenedor en ejecución a medida que guardas los archivos.

1. Actualiza `compose.yaml` para añadir el bloque `develop.watch` al servicio `web`:

   ```yaml
   services:
     web:
       build: .
       ports:
         - "${APP_PORT}:5000"
       environment:
         - REDIS_HOST=${REDIS_HOST}
         - REDIS_PORT=${REDIS_PORT}
       depends_on:
         redis:
           condition: service_healthy
       develop:
         watch:
           - action: sync+restart
             path: .
             target: /code
           - action: rebuild
             path: requirements.txt

     redis:
       image: redis:alpine
       healthcheck:
         test: ["CMD", "redis-cli", "ping"]
         interval: 5s
         timeout: 3s
         retries: 5
         start_period: 10s
   ```

   El bloque `watch` define dos reglas:
   - La acción `sync+restart` vigila el directorio de tu proyecto (`.`) en el host. Cuando un archivo cambia, Compose copia los archivos modificados en `/code` dentro del contenedor en ejecución y luego reinicia el contenedor. Debido a que el contenedor se reinicia con los archivos actualizados ya en su lugar, Flask se inicia leyendo el nuevo código directamente, sin necesidad de una reconstrucción o reinicio manual.
   - La acción `rebuild` en `requirements.txt` desencadena una reconstrucción completa de la imagen cada vez que añades una nueva dependencia, ya que instalar paquetes requiere reconstruir la imagen y no consiste únicamente en sincronizar archivos.

2. Inicia el stack con Watch habilitado:

   ```console
   $ docker compose up --watch
   ```

3. Realiza un cambio en vivo. Abre `app.py` y actualiza el saludo:

   ```python
   return f"Hello from Compose Watch! I have been seen {count} time(s).\n"
   ```

4. Guarda el archivo. Compose Watch detecta el cambio y lo sincroniza de inmediato:

   ```text
   Syncing service "web" after changes were detected
   ```

5. Actualiza `http://localhost:8000`. El saludo actualizado aparece sin ningún reinicio y el contador debería seguir incrementándose.

6. Detén el stack antes de continuar:

   ```console
   $ docker compose down
   ```

   Para obtener más información sobre cómo funciona Compose Watch, consulta [Usar Compose Watch](/compose/how-tos/file-watch/).

## Paso 5: Persistir datos con volúmenes con nombre

Cada vez que detienes y reinicias el stack, el contador de visitas se restablece a cero. Los datos de Redis viven dentro del contenedor, por lo que desaparecen cuando este se elimina. Un volumen con nombre soluciona esto almacenando los datos en el host, fuera del ciclo de vida del contenedor.

1. Update `compose.yaml`:

   ```yaml
   services:
     web:
       build: .
       ports:
         - "${APP_PORT}:5000"
       environment:
         - REDIS_HOST=${REDIS_HOST}
         - REDIS_PORT=${REDIS_PORT}
       depends_on:
         redis:
           condition: service_healthy
       develop:
         watch:
           - action: sync+restart
             path: .
             target: /code
           - action: rebuild
             path: requirements.txt

     redis:
       image: redis:alpine
       volumes:
         - redis-data:/data
       healthcheck:
         test: ["CMD", "redis-cli", "ping"]
         interval: 5s
         timeout: 3s
         retries: 5
         start_period: 10s

   volumes:
     redis-data:
   ```

   La entrada `redis-data:/data` bajo `redis.volumes` monta el volumen con nombre en `/data`, la ruta donde Redis escribe sus archivos de datos. La clave `volumes` de nivel superior lo registra con Docker para que persista entre los ciclos de `compose down` y `compose up`.

2. Inicia el stack con `docker compose up --watch` y actualiza `http://localhost:8000` unas cuantas veces para aumentar el contador.

3. Destruye el stack con `docker compose down` y luego vuelve a levantarlo con `docker compose up --watch`.

4. Abre `http://localhost:8000`: el contador continúa desde donde se quedó.

5. Ahora restablece el contador con `docker compose down -v`.

   La bandera `-v` elimina los volúmenes con nombre junto con los contenedores. Utilízala de forma intencionada: elimina permanentemente los datos almacenados.

## Paso 6: Estructurar tu proyecto con múltiples archivos de Compose

A medida que las aplicaciones crecen, un único archivo `compose.yaml` se vuelve más difícil de mantener. El elemento de nivel superior `include` te permite dividir los servicios en múltiples archivos mientras continúan formando parte de la misma aplicación.

Esto es especialmente útil cuando diferentes equipos son propietarios de distintas partes del stack, o cuando quieres reutilizar definiciones de infraestructura en varios proyectos.

1. Crea un nuevo archivo en el directorio de tu proyecto llamado `infra.yaml` y mueve el servicio Redis y el volumen dentro de él:

   ```yaml
    services:
     redis:
       image: redis:alpine
       volumes:
         - redis-data:/data
       healthcheck:
         test: ["CMD", "redis-cli", "ping"]
         interval: 5s
         timeout: 3s
         retries: 5
         start_period: 10s

   volumes:
     redis-data:
   ```

2. Actualiza `compose.yaml` para incluir `infra.yaml`:

   ```yaml
   include:
     - path: ./infra.yaml
   services:
     web:
       build: .
       ports:
         - "${APP_PORT}:5000"
       environment:
         - REDIS_HOST=${REDIS_HOST}
         - REDIS_PORT=${REDIS_PORT}
       depends_on:
         redis:
           condition: service_healthy
       develop:
         watch:
           - action: sync+restart
             path: .
             target: /code
           - action: rebuild
             path: requirements.txt
   ```

3. Ejecuta la aplicación para confirmar que todo sigue funcionando:

   ```console
   $ docker compose up --watch
   ```

   Compose fusiona ambos archivos en el inicio. El servicio `web` aún puede hacer referencia a `redis` por su nombre porque todos los servicios incluidos comparten la misma red predeterminada.

   Este es un ejemplo simplificado, pero demuestra el principio básico de `include` y cómo puede facilitar la modularización de aplicaciones complejas en archivos secundarios de Compose. Para obtener más información sobre `include` y cómo trabajar con múltiples archivos de Compose, consulta [Trabajar con múltiples archivos de Compose](/compose/how-tos/multiple-compose-files/).

4. Detén el stack antes de continuar:

   ```console
   $ docker compose down
   ```

## Paso 7: Inspeccionar y depurar tu stack en ejecución

Con un stack completamente configurado, puedes observar lo que sucede dentro de tus contenedores sin detener nada. Este paso cubre los comandos principales para inspeccionar la configuración resuelta, transmitir logs y ejecutar comandos dentro de un contenedor en ejecución.

Antes de iniciar el stack, verifica que Compose haya resuelto tus variables de `.env` y fusionado todos los archivos correctamente:

```console
$ docker compose config
```

`docker compose config` no requiere que el stack esté en ejecución: funciona exclusivamente a partir de tus archivos. Algunos detalles a observar en la salida:

- `${APP_PORT}`, `${REDIS_HOST}` y `${REDIS_PORT}` han sido reemplazados por los valores de tu archivo `.env`.
- La notación corta de puertos (`"8000:5000"`) se expande en sus campos canónicos (`target`, `published`, `protocol`).
- Los nombres de la red y del volumen predeterminados se hacen explícitos, prefijados con el nombre del proyecto `compose-demo`.
- La salida es la configuración completamente resuelta, con los archivos incorporados a través de `include` fusionados en una única vista.

Utiliza `docker compose config` en cualquier momento que quieras confirmar lo que Compose realmente aplicará, especialmente al depurar la sustitución de variables o al trabajar con múltiples archivos de Compose.

Ahora inicia el stack en modo detached (segundo plano) para que la terminal quede libre para los siguientes comandos:

```console
$ docker compose up -d
```

### Transmitir logs de todos los servicios

```console
$ docker compose logs -f
```

La bandera `-f` sigue el flujo de logs en tiempo real, intercalando la salida de ambos contenedores con prefijos de nombre de servicio codificados por colores. Actualiza `http://localhost:8000` unas cuantas veces y observa cómo aparecen los logs de solicitud de Flask. Para seguir los logs de un único servicio, pasa su nombre:

```console
$ docker compose logs -f web
```

Presiona `Ctrl+C` para dejar de seguir los logs. Los contenedores continúan ejecutándose.

### Ejecutar comandos dentro de un contenedor en ejecución

`docker compose exec` ejecuta un comando dentro de un contenedor que ya está en ejecución sin iniciar uno nuevo. Esta es la herramienta principal para la depuración en vivo.

#### Verificar que las variables de entorno estén configuradas correctamente

```console
$ docker compose exec web env | grep REDIS
```

```text
REDIS_HOST=redis
REDIS_PORT=6379
```

#### Probar que el contenedor `web` puede comunicarse con Redis utilizando el nombre del servicio como nombre de host

```console
$ docker compose exec web python -c "import redis; r = redis.Redis(host='redis'); print(r.ping())"
```

```text
True
```

Esto utiliza la misma biblioteca `redis` que usa tu aplicación, por lo que una respuesta `True` confirma que el descubrimiento de servicios, la red y la conexión a Redis están funcionando de extremo a extremo.

#### Inspeccionar el valor en tiempo real del contador de visitas en Redis

```console
$ docker compose exec redis redis-cli GET hits
```

## Siguientes pasos

- [Explora la lista completa de comandos de Compose](/reference/cli/docker/compose/)
- [Explora la referencia del archivo de Compose](/reference/compose-file/)
- [Mira el video Learning Docker Compose en LinkedIn Learning](https://www.linkedin.com/learning/learning-docker-compose/)
- [Aprende cómo configurar variables de entorno en Compose](/compose/how-tos/environment-variables/set-environment-variables/)
- [Aprende cómo empaquetar y distribuir tu aplicación de Compose](/compose/how-tos/oci-artifact/)

