De varios contenedores a una aplicación
En el proyecto final de Docker 1 pusiste en marcha TasteMatch junto a Redis. Construiste una imagen, creaste una red y almacenamiento persistente, proporcionaste variables, publicaste un puerto y comprobaste cada recurso.
Todo funcionaba. Entonces, ¿qué problema queda por resolver?
Imagina que mañana eliminas los contenedores y quieres reconstruir exactamente el mismo sistema. O que otra persona descarga TasteMatch y necesita ejecutarlo en su equipo. Tendrá que conocer no solo la imagen de la aplicación, sino también cada nombre, red, volumen, puerto, variable y relación entre recursos.
imagen de TasteMatch
contenedor de TasteMatch
imagen de Redis
contenedor de Redis
red compartida
volumen de favoritos
variables de configuración
puerto publicado
orden de las operaciones
Sabemos gestionar cada pieza. Ahora vamos a observar qué ocurre cuando todas ellas forman una sola aplicación.
Aplicación y servicio no significan lo mismo
TasteMatch es el sistema completo que ofrecemos al usuario. Para funcionar necesita varias partes que colaboran:
TasteMatch
├── aplicación web
└── caché Redis
Llamaremos servicio a cada parte de la aplicación que cumple una función concreta.
aplicación
→ sistema completo que queremos ejecutar
servicio
→ una parte de ese sistema
En este caso, TasteMatch es la aplicación; la web y Redis son servicios. Eso no convierte el proyecto en una arquitectura de microservicios. Estamos utilizando una palabra útil para distinguir el conjunto de sus partes.
Un servicio tampoco es exactamente un contenedor. El servicio describe la función y las necesidades de una parte de la aplicación; el contenedor es un recurso Docker que puede ejecutar esa parte.
Inventario antes de ejecutar
Recupera el proyecto final de Docker 1 y la imagen que construiste. En los comandos de esta unidad utilizaremos este nombre:
tastematch-fundamentos:1.0
Si tu imagen tiene otro nombre o tag, sustitúyelo de forma consistente.
TasteMatch necesita este inventario:
| Pieza | Decisión |
|---|---|
| Servicio web | Imagen tastematch-fundamentos:1.0 |
| Caché | Imagen redis:7-alpine |
| Red | dk201-red |
| Almacenamiento | Volumen dk201-favoritos |
| Contenedor web | dk201-app |
| Contenedor Redis | dk201-redis |
| Puerto interno web | 3000 |
| Publicación | 8080:3000 |
| Host de caché | dk201-redis |
| Puerto de caché | 6379 |
| Directorio de datos | /app/data |
La tabla ya es una forma de documentación. Pero Docker todavía no puede interpretarla: nosotros tendremos que convertir cada fila en una operación.
El starter de TasteMatch sigue disponible en el ZIP del proyecto final de Docker 1. Esta unidad presupone que ya resolviste aquel proyecto y construiste su imagen. No vamos a repetir el Dockerfile ni el proceso de build.
Ejecutar la aplicación manualmente
Utiliza una terminal situada en cualquier carpeta: estos comandos trabajan con recursos Docker nombrados y no dependen de archivos nuevos.
Antes de empezar, comprueba que no existen recursos anteriores con los nombres dk201-*. No elimines recursos si no sabes de dónde proceden.
1. Crear la red
docker network create dk201-red
2. Crear el volumen
docker volume create dk201-favoritos
3. Iniciar Redis
docker run -d --name dk201-redis --network dk201-red redis:7-alpine
4. Iniciar TasteMatch
El comando se mantiene en una sola línea para que funcione igual en Bash y PowerShell:
docker run -d --name dk201-app --network dk201-red -p 8080:3000 -e APP_MODE=development -e CACHE_HOST=dk201-redis -e CACHE_PORT=6379 -e DATA_DIR=/app/data -v dk201-favoritos:/app/data tastematch-fundamentos:1.0
No estudies de nuevo cada opción: ya las utilizaste en Docker 1. Esta vez observa cuántas decisiones quedan reunidas en un único comando y cuántas dependen de los pasos anteriores.
5. Comprobar el resultado
docker ps --filter name=dk201-
docker logs dk201-app
docker network inspect dk201-red
docker volume inspect dk201-favoritos
Abre:
http://localhost:8080/health
La respuesta debe indicar que la aplicación está activa y que alcanza la caché. Después consulta /recipes y /favorites.
Si algo falla, no repitas todos los comandos. Comprueba estado, logs, publicación, variables, montaje y miembros de la red como aprendiste en Docker 1.
Funciona… ¿cuál es entonces el problema?
La gestión manual no es incorrecta. Acabamos de construir una aplicación funcional utilizando recursos Docker explícitos.
El problema aparece cuando queremos repetir el resultado. Para reconstruirlo necesitamos recordar:
- qué imágenes y tags usamos;
- qué nombres asignamos;
- qué recurso se crea primero;
- qué red comparten los contenedores;
- qué volumen se monta y en qué ruta;
- qué puerto publicamos;
- qué variables necesita TasteMatch;
- qué valores pertenecen al laboratorio;
- qué recursos debemos conservar o eliminar.
Prueba a cerrar la terminal. ¿Podrías escribir mañana el comando de TasteMatch exactamente igual sin consultar nada?
Repetir no es copiar a ciegas
Podríamos guardar los comandos en un documento:
1. crea dk201-red
2. crea dk201-favoritos
3. inicia Redis con este nombre y esta red
4. inicia TasteMatch con esta red, publicación, volumen y variables
5. comprueba los endpoints
Eso mejora la situación, pero presenta nuevos riesgos:
- el documento puede quedar desactualizado;
- un comando puede cambiar sin que cambie la explicación;
- otra persona puede ejecutar solo parte de la secuencia;
- la configuración queda repartida entre Dockerfile, README y terminal;
- detener o eliminar el conjunto requiere volver a identificar sus recursos;
- comparar dos versiones exige revisar instrucciones sueltas.
El problema no es la longitud por sí sola. Es que la estructura de la aplicación solo existe en nuestra memoria y en una secuencia de acciones.
¿Qué ocurre al compartirla?
Supón que una compañera recibe:
- el código de TasteMatch;
- su Dockerfile;
- un README con los comandos anteriores.
Puede construir la imagen, pero aún tiene que interpretar varias relaciones:
TasteMatch usa Redis
→ ambos deben compartir red
→ CACHE_HOST debe señalar el nombre de Redis
TasteMatch escribe favoritos
→ DATA_DIR debe coincidir con el destino del volumen
el navegador usa TasteMatch
→ solo el puerto web necesita publicarse
Una errata en CACHE_HOST o en /app/data no produce la misma aplicación. Compartir archivos no basta si las relaciones dependen de instrucciones informales.
Mantenerla durante meses
Ahora imagina tres cambios separados:
- el puerto del host pasa de
8080a8081; - la imagen de TasteMatch pasa a
1.1; - añadimos una variable de configuración.
Tendríamos que actualizar los comandos, los ejemplos y quizá el procedimiento de limpieza. Si conservamos varias copias del comando en distintos documentos, pueden contradecirse.
Una aplicación reproducible necesita que sus decisiones importantes estén expresadas de una manera clara, revisable y cercana al código.
Acciones frente a descripción
Hasta ahora hemos utilizado un enfoque principalmente imperativo:
crea esta red
crea este volumen
ejecuta este contenedor
ejecuta después este otro
Indicamos acciones concretas y su orden:
docker network create ...
docker volume create ...
docker run ...
docker run ...
Podemos plantear el problema de otra forma: en vez de guardar únicamente la secuencia, describimos el resultado que queremos.
Mi aplicación tiene:
- un servicio web construido con nuestra imagen;
- un servicio Redis basado en una imagen existente;
- una red compartida;
- un volumen para favoritos;
- determinadas variables;
- un puerto web publicado.
Este enfoque es declarativo a un nivel introductorio: expresamos cómo debe estar compuesto el sistema y dejamos que una herramienta realice buena parte de las operaciones necesarias.
No significa que el orden, los errores o el ciclo de vida desaparezcan. Tampoco necesitamos estudiar ahora teoría de paradigmas. La diferencia útil es esta:
imperativo
→ lista de acciones
declarativo
→ descripción del resultado y sus relaciones
Describir TasteMatch antes de elegir un formato
Todavía no vamos a escribir YAML. Primero expresa la aplicación con lenguaje estructurado:
APLICACIÓN TasteMatch
SERVICIO app
imagen tastematch-fundamentos:1.0
puerto interno 3000
publicación 8080 → 3000
APP_MODE = development
CACHE_HOST = dk201-redis
CACHE_PORT = 6379
DATA_DIR = /app/data
utiliza red interna
monta volumen de favoritos en /app/data
SERVICIO redis
imagen redis:7-alpine
puerto interno 6379
utiliza red interna
no publica puerto al host
RED
conecta app y redis
VOLUMEN
conserva favoritos fuera del contenedor app
Esta descripción muestra las piezas y sus relaciones sin obligarnos a reconstruir mentalmente un comando largo.
Predice:
- ¿qué cambio afecta solo a
app?; - ¿qué recurso comparten ambos servicios?;
- ¿qué dato debe sobrevivir a un contenedor?;
- ¿qué puerto necesita el navegador?;
- ¿qué puerto no necesita publicarse?;
Qué ganamos al guardar la descripción
Reproducibilidad
La definición puede servir como punto de partida para recrear la misma estructura. Aun así, también importan el código, las versiones de imágenes, la configuración y las dependencias externas.
Documentación
Una descripción legible muestra buena parte de la arquitectura. No sustituye un README que explique requisitos, acceso, pruebas y decisiones.
Versionado
Podemos guardar la definición junto al proyecto y revisar cómo cambian servicios, puertos o relaciones. Nunca debemos incluir secretos por el simple hecho de versionar el archivo.
Trabajo en equipo
Las personas parten de la misma definición en lugar de reconstruirla desde fragmentos de conversación o historial de terminal.
Mantenimiento
Las decisiones relacionadas quedan en un lugar conocido. Un cambio sigue necesitando revisión y comprobación, pero resulta más visible.
La herramienta que utilizaremos
Docker Compose permite definir y ejecutar aplicaciones formadas por varios contenedores.
descripción de la aplicación
↓
compose.yaml
↓
docker compose
↓
contenedores · redes · volúmenes · configuración
Compose no sustituye Docker. Interpreta la descripción y crea o gestiona recursos Docker que ya conoces. Una red mal planteada seguirá siendo una red mal planteada; un puerto incorrecto seguirá impidiendo el acceso; una imagen rota seguirá sin funcionar.
Su utilidad tampoco consiste únicamente en ahorrar escritura. El cambio importante es poder describir la aplicación como un conjunto.
En esta unidad no escribiremos aún compose.yaml ni ejecutaremos comandos de Compose. La siguiente unidad transformará nuestra descripción conceptual en el primer archivo real, empezando por un solo servicio y observando cada recurso creado.
Errores conceptuales habituales
«Compose sustituye Docker»
No. Compose trabaja con imágenes, contenedores, redes y volúmenes Docker que ya conocemos, y gestiona esos recursos según la descripción de la aplicación.
«Compose solo sirve para escribir menos comandos»
También centraliza una descripción versionable de servicios y relaciones. Reducir pasos manuales es una consecuencia, no todo su valor.
«Un servicio siempre es un contenedor»
Son conceptos relacionados, pero distintos. Aquí veremos normalmente un contenedor por servicio; no necesitamos introducir todavía escalado ni réplicas.
«Servicio significa microservicio»
No. Redis y la aplicación web cumplen funciones distintas sin convertir esta unidad en un curso de arquitectura de microservicios.
«Compose arreglará mi configuración»
No puede decidir por nosotros qué hostname, ruta o puerto son correctos. Necesitamos comprender las piezas para describirlas bien y diagnosticar el resultado.
«Una definición garantiza reproducibilidad perfecta»
Ayuda, pero también debemos controlar código, imágenes, tags, configuración permitida, datos y dependencias externas.
Mini reto: describe una aplicación sin YAML
Una aplicación nueva tiene estos requisitos:
backend
├── imagen propia tastematch-api:1.0
├── escucha en 3000
├── publicación 8080 → 3000
├── DB_HOST = db
├── DB_PORT = 5432
└── red app-interna
base de datos
├── imagen PostgreSQL con un tag concreto
├── nombre lógico db
├── puerto interno 5432
├── volumen datos-db
└── red app-interna
Sin escribir YAML ni utilizar Compose:
- identifica la aplicación completa y sus dos servicios;
- separa imágenes, contenedores, red, volumen, variables y publicación;
- escribe el orden aproximado de operaciones manuales;
- indica qué puerto debe usar el backend para llegar a la base;
- explica por qué la base no necesita publicar
5432para ese acceso interno; - señala qué información tendría que recibir otra persona;
- convierte los requisitos en una descripción estructurada como la de TasteMatch;
- marca qué datos deben sobrevivir a la eliminación del contenedor de base de datos;
- enumera dos formas en que un README de comandos podría quedar desactualizado.
Compara al final:
lista de operaciones manuales
frente a
descripción de recursos y relaciones
¿Qué partes de tu descripción debería poder interpretar una herramienta para crear la aplicación? Esa pregunta abre la siguiente unidad: convertir la descripción en compose.yaml.
Limpiar el laboratorio manual
Cuando termines de observar la aplicación de Docker 1, elimina únicamente los recursos creados en esta unidad:
docker rm -f dk201-app dk201-redis
docker network rm dk201-red
docker volume rm dk201-favoritos
El último comando elimina los favoritos del laboratorio. Ejecútalo solo después de decidir que ya no necesitas esos datos. No elimina la imagen tastematch-fundamentos:1.0 ni otros recursos de Docker.