Saltar al contenido principal

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:

PiezaDecisión
Servicio webImagen tastematch-fundamentos:1.0
CachéImagen redis:7-alpine
Reddk201-red
AlmacenamientoVolumen dk201-favoritos
Contenedor webdk201-app
Contenedor Redisdk201-redis
Puerto interno web3000
Publicación8080: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.

Punto de partida reproducible

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:

  1. el puerto del host pasa de 8080 a 8081;
  2. la imagen de TasteMatch pasa a 1.1;
  3. 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:

  1. identifica la aplicación completa y sus dos servicios;
  2. separa imágenes, contenedores, red, volumen, variables y publicación;
  3. escribe el orden aproximado de operaciones manuales;
  4. indica qué puerto debe usar el backend para llegar a la base;
  5. explica por qué la base no necesita publicar 5432 para ese acceso interno;
  6. señala qué información tendría que recibir otra persona;
  7. convierte los requisitos en una descripción estructurada como la de TasteMatch;
  8. marca qué datos deben sobrevivir a la eliminación del contenedor de base de datos;
  9. 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.