Docker Compose
En la unidad anterior describimos TasteMatch como un conjunto de servicios, red, almacenamiento, configuración y puertos. Esa descripción era legible, pero Docker todavía no podía interpretarla.
Ahora construiremos el primer archivo compose.yaml. Empezaremos con un único servicio para poder relacionar cada línea con Docker 0 y Docker 1 antes de ampliar la aplicación.
compose.yaml
↓
docker compose
↓
recursos Docker reales
Preparar una aplicación mínima
Crea una carpeta dk202-compose con estos archivos:
dk202-compose/
├── app.js
├── Dockerfile
├── app.env
└── compose.yaml
app.js utiliza únicamente Node.js:
const http = require("node:http");
const port = Number(process.env.PORT || 3000);
const appName = process.env.APP_NAME || "TasteMatch";
const mode = process.env.APP_MODE || "development";
const server = http.createServer((request, response) => {
response.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
response.end(JSON.stringify({ app: appName, mode, path: request.url }));
});
server.listen(port, "0.0.0.0", () => {
console.log(`${appName} escucha en ${port} (${mode}).`);
});
Dockerfile:
FROM node:22-alpine
WORKDIR /app
COPY app.js .
CMD ["node", "app.js"]
No hay dependencias que instalar. El objetivo es estudiar Compose, no Node.js.
YAML: solo lo que necesitamos
Compose utiliza YAML. Tres formas bastan para empezar.
Clave y valor:
image: nginx:1.27-alpine
Anidación mediante espacios:
services:
web:
image: nginx:1.27-alpine
Lista:
ports:
- "8080:80"
La indentación expresa qué pertenece a qué. Utiliza espacios, no tabuladores. Esto es incorrecto porque image no queda dentro de web:
services:
web:
image: nginx:1.27-alpine
No necesitamos anchors, aliases ni otras funciones avanzadas de YAML.
El primer compose.yaml
Empieza con un servicio basado en una imagen existente:
services:
web:
image: nginx:1.27-alpine
Lee antes de ejecutar:
services → colección de servicios
web → nombre lógico del servicio
image → imagen que utilizará
Un servicio es una definición. Compose la interpreta y crea o gestiona el contenedor necesario. No hemos publicado puertos, declarado volúmenes ni escrito una red manualmente.
No incluimos la clave raíz version: la Compose Specification actual no la necesita.
Validar antes de levantar
Desde la carpeta que contiene compose.yaml:
docker compose config
Este comando comprueba y muestra la configuración resuelta. Úsalo después de cada cambio importante. Si hay un error de indentación o estructura, corrígelo antes de crear recursos.
Levantar el primer servicio
docker compose up
Compose lee la definición, determina qué necesita, crea los recursos y arranca el servicio. En primer plano verás los logs. Detén la ejecución con Ctrl+C y repite en segundo plano:
docker compose up -d
Consulta el estado:
docker compose ps
Compáralo con:
docker ps
docker network ls
Compose no inventó otro tipo de contenedor o red: organizó recursos Docker reales dentro de un proyecto.
Publicar el puerto
Modifica el servicio:
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
La publicación mantiene el modelo conocido:
HOST:CONTENEDOR
8080:80
Es la misma decisión que docker run -p 8080:80 ..., expresada en la definición. Aplica el cambio:
docker compose config
docker compose up -d
Abre http://localhost:8080. Observa con docker compose ps qué recurso tuvo que recrear Compose.
De una imagen existente a nuestra aplicación
Antes de cambiar la definición, cierra el primer laboratorio. Mientras compose.yaml todavía contiene el servicio web, ejecuta:
docker compose down
Así eliminas su contenedor y liberas el puerto 8080. Quitar web del archivo no elimina por sí solo el contenedor anterior.
Después reemplaza el contenido de compose.yaml por la definición de app para comenzar el nuevo laboratorio:
services:
app:
build: .
ports:
- "8080:3000"
La diferencia es importante:
image
→ qué imagen utilizar
build
→ cómo construir la imagen necesaria
build: . utiliza la carpeta actual como contexto, igual que en Docker 1. Compose encontrará allí el Dockerfile.
Construye de forma explícita:
docker compose build
Después levanta:
docker compose up -d
Si modificas app.js, el contenedor existente no recibe ese cambio automáticamente. Puedes construir y aplicar el resultado:
docker compose build
docker compose up -d
También existe:
docker compose up -d --build
Utilízalo cuando realmente quieras reconstruir antes de levantar, no como reflejo para cualquier cambio. Variables o puertos no requieren reconstruir la imagen.
Configuración con environment
Añade variables que recibirá el contenedor:
services:
app:
build: .
ports:
- "8080:3000"
environment:
PORT: 3000
APP_NAME: TasteMatch
APP_MODE: development
Esto expresa la misma intención que docker run -e. Comprueba:
docker compose config
docker compose up -d
docker compose logs app
Abre http://localhost:8080 y observa los valores de la respuesta.
Separar valores con env_file
Guarda en app.env:
PORT=3000
APP_NAME=TasteMatch
APP_MODE=development
Y utiliza:
services:
app:
build: .
ports:
- "8080:3000"
env_file:
- app.env
env_file proporciona variables al contenedor. Este archivo contiene valores de laboratorio, no secretos reales ni un mecanismo profesional de protección.
Interpolación: variables para resolver Compose
Queremos elegir el puerto del host sin editar el YAML:
services:
app:
build: .
ports:
- "${APP_PORT}:3000"
env_file:
- app.env
Crea .env junto a compose.yaml:
APP_PORT=8080
Distingue los dos recorridos:
.env y ${APP_PORT}
→ Compose resuelve su configuración
app.env y env_file
→ el contenedor recibe variables
No asumas que env_file alimenta cualquier ${...}. Comprueba siempre el resultado:
docker compose config
Estado, logs y comandos
docker compose ps
docker compose logs
docker compose logs app
docker compose logs -f app
logs -f sigue la salida hasta Ctrl+C; no detiene el servicio.
Ejecuta un comando dentro del contenedor activo:
docker compose exec app node --version
docker compose exec app printenv APP_MODE
Es el equivalente orientado al servicio de docker exec. Requiere que el contenedor esté en ejecución.
Detener, iniciar y eliminar
docker compose stop
Detiene los servicios y conserva sus contenedores. Comprueba docker compose ps -a y vuelve a iniciarlos:
docker compose start
Para retirar los contenedores y determinados recursos creados para el proyecto:
docker compose down
stop → detiene y conserva contenedores
start → inicia los contenedores conservados
down → detiene y elimina contenedores y determinados recursos del proyecto
down no significa «borra todo». La persistencia y el tratamiento de volúmenes llegarán en DK2-04.
El proyecto Compose
Compose agrupa recursos bajo un proyecto, normalmente derivado del directorio. Observa los nombres mostrados por:
docker compose ps -a
docker network ls
docker image ls
No añadas container_name para obtener nombres predecibles. Compose ya conoce el servicio app; en la siguiente unidad utilizaremos nombres de servicio para comunicar contenedores.
Errores habituales
YAML mal indentado
Ejecuta docker compose config: una clave al nivel equivocado cambia el significado o invalida el archivo.
Confundir image y build
image selecciona una imagen; build describe su construcción. No son dos formas decorativas de escribir lo mismo.
Invertir los puertos
En 8080:3000, el navegador usa 8080 y la aplicación escucha en 3000.
Pensar que up siempre reconstruye
Un cambio de código copiado en la imagen requiere otro build. Usa build o up --build con intención.
Reconstruir ante cualquier cambio
Cambiar una variable o publicación modifica la ejecución, no necesariamente la imagen.
Confundir env_file e interpolación
Observa la configuración resuelta y comprueba por separado el entorno del contenedor.
Añadir container_name por costumbre
Limita decisiones de Compose y no es necesario para el flujo normal del curso.
Ejecutar desde otra carpeta
Trabaja desde la carpeta de compose.yaml, salvo que indiques explícitamente otra ruta mediante opciones que todavía no necesitamos.
Mini reto
Partiendo de app.js y el Dockerfile de esta unidad:
- crea un
compose.yamldesde cero; - construye el servicio
appconbuild; - publica el puerto
8090del host hacia3000; - proporciona
APP_NAME=Skilly,APP_MODE=challengeyPORT=3000mediante un archivo; - valida con
docker compose config; - construye y levanta en segundo plano;
- comprueba estado, respuesta HTTP y logs;
- ejecuta
printenv APP_NAMEdentro del servicio; - cambia solo
APP_MODEy razona si hace falta otro build; - detén con
stopy comprueba que el contenedor existe; - recupera con
start; - retira el proyecto con
down; - compara recursos antes y después.
Explica qué línea representa docker build, docker run -p y docker run -e. En la próxima unidad añadiremos un segundo servicio y estudiaremos la red que Compose ya ha empezado a gestionar.