Servicios y redes
En DK2-02 definimos un servicio. Ahora TasteMatch necesita colaborar con una base de datos:
host :8080 → app :3000 → db :5432
Ya conoces redes, nombres y puertos por Docker 1. Aquí veremos cómo Compose describe y gestiona esas mismas piezas.
Servicio, definición y contenedor
servicio app en compose.yaml
→ definición de cómo ejecutar esa parte
→ Compose crea y gestiona su contenedor
app es un nombre lógico estable dentro del proyecto. No necesitamos predecir el nombre ni copiar el ID del contenedor, ni añadir container_name. Servicio y contenedor están relacionados, pero no son sinónimos.
Laboratorio app + db
Utiliza el app.js y Dockerfile de DK2-02. La imagen node:22-alpine ya contiene Node.js; lo usaremos también para comprobar TCP sin instalar curl, ping ni paquetes durante la ejecución.
Crea este compose.yaml:
services:
app:
build: .
ports:
- "8080:3000"
environment:
DB_HOST: db
DB_PORT: 5432
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: tastematch
POSTGRES_USER: tastematch
POSTGRES_PASSWORD: laboratorio
Las credenciales son solo para este laboratorio. Todavía no añadimos volumen, depends_on ni healthcheck.
Antes de ejecutar, identifica dos servicios, un único puerto publicado y el destino interno db:5432. PostgreSQL no tiene ports: app no necesita salir al host para alcanzarlo.
La red automática
docker compose config
docker compose up -d
docker compose ps
docker network ls
Aunque no declaramos networks, Compose crea normalmente una red predeterminada del proyecto y conecta ambos servicios:
red predeterminada
├── app
└── db
El nombre real incorpora normalmente el nombre del proyecto. Localízalo en docker network ls en vez de memorizarlo e inspecciónalo:
docker network inspect <red-del-proyecto>
Busca nombre, driver y contenedores conectados. Las IP sirven como evidencia puntual, no como configuración de la aplicación.
Esto corresponde, a alto nivel, al trabajo manual conocido:
docker network create <red>
docker run --network <red> ...
docker run --network <red> ...
Compose no crea networking mágico: administra redes Docker reales a partir de la definición.
Nombres de servicio y DNS
Los servicios que comparten una red pueden resolver sus nombres. Desde app:
db
↓ DNS proporcionado por Docker en la red
contenedor del servicio db
Compruébalo con una herramienta que sabemos que existe en la imagen de la aplicación:
docker compose exec app node -e "require('node:dns').lookup('db',(e,a)=>{if(e)throw e;console.log(a)})"
La dirección mostrada puede cambiar. El valor que configuramos sigue siendo db.
Comprueba además que el puerto acepta conexiones TCP:
docker compose exec app node -e "const s=require('node:net').connect(5432,'db',()=>{console.log('db:5432 accesible');s.end()});s.on('error',e=>{console.error(e.message);process.exitCode=1})"
Esta prueba demuestra conectividad, no que la aplicación haya ejecutado una consulta ni que PostgreSQL esté preparado en cualquier instante.
localhost no es el otro servicio
Dentro de app:
localhost → app
db → servicio db
Provoca una prueba controlada:
docker compose exec app node -e "const s=require('node:net').connect(5432,'localhost',()=>s.end());s.on('error',e=>{console.error(e.message);process.exitCode=1})"
El fallo no implica que PostgreSQL esté roto: estamos preguntando por el puerto 5432 dentro de app.
Puerto interno y publicación
navegador → localhost:8080 → app:3000
app → db:5432
ports publica hacia el host. La comunicación interna usa nombre de servicio y puerto interno. Publicar 5432 no es requisito para que app llegue a db.
Declarar una red explícita
Después de entender la predeterminada, expresa la relación:
services:
app:
build: .
ports:
- "8080:3000"
environment:
DB_HOST: db
DB_PORT: 5432
networks:
- datos
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: tastematch
POSTGRES_USER: tastematch
POSTGRES_PASSWORD: laboratorio
networks:
- datos
networks:
datos:
La sección raíz declara la red lógica; la sección de cada servicio indica su conexión. Valida, aplica e inspecciona:
docker compose config
docker compose up -d
docker compose ps
docker network ls
docker network inspect <red-datos-del-proyecto>
Un servicio en dos redes
Añade una topología con tres servicios:
services:
web:
image: nginx:1.27-alpine
networks:
- publica
app:
build: .
networks:
- publica
- datos
db:
image: postgres:17-alpine
environment:
POSTGRES_DB: tastematch
POSTGRES_USER: tastematch
POSTGRES_PASSWORD: laboratorio
networks:
- datos
networks:
publica:
datos:
web ── publica ── app ── datos ── db
web resuelve app; app resuelve web y db; web y db no comparten una red directa.
Valida y aplica esta definición antes de comprobarla:
docker compose config
docker compose up -d
docker compose ps
La imagen nginx:1.27-alpine incluye nc de BusyBox. Lo utilizaremos desde el propio servicio web, sin instalar paquetes: -z comprueba la conexión sin enviar datos, -v muestra el resultado y -w 3 limita la espera de conexión a tres segundos. La resolución DNS puede tardar algo más.
Comprueba los tres recorridos:
docker compose exec web nc -z -v -w 3 app 3000
docker compose exec app node -e "const s=require('node:net').connect(5432,'db',()=>{console.log('db:5432 accesible');s.end()});s.on('error',e=>{console.error(e.message);process.exitCode=1})"
docker compose exec web nc -z -v -w 3 db 5432
Los dos primeros deben conectar: web alcanza app:3000 y app alcanza db:5432. El tercero debe fallar con un código de salida distinto de cero: desde web, db no se resuelve porque no comparten red. Es el fallo esperado, no una avería de PostgreSQL. Que app esté en ambas redes no lo convierte automáticamente en un intermediario para web.
Como segunda evidencia, localiza e inspecciona ambas redes:
docker network ls
docker network inspect <red-publica-del-proyecto>
docker network inspect <red-datos-del-proyecto>
En publica deben aparecer web y app; en datos, app y db. Combina las conexiones y el fallo observados con estas membresías para explicar el aislamiento directo.
Las redes expresan conectividad, pero no constituyen por sí solas una estrategia completa de seguridad.
Diagnóstico ordenado
Ante un fallo:
estado → logs → hostname/puerto → redes compartidas → prueba desde consumidor
docker compose ps
docker compose logs app
docker compose config
docker network inspect <red>
docker compose exec app node -e "require('node:dns').lookup('db',(e,a)=>console.log(e||a))"
No cambies hostname, puerto y redes simultáneamente. Formula una hipótesis y conserva evidencia.
Errores habituales
localhostapunta al contenedor actual, no adb.- El puerto publicado del host no es la ruta normal entre servicios.
- No hace falta publicar todos los puertos internos.
- Dos servicios sin red compartida no deben asumir resolución directa.
- Las IP internas pueden cambiar; usa nombres.
- El nombre del servicio no es el ID ni el nombre generado del contenedor.
container_nameno es necesario para el DNS del servicio.- Una red Compose sigue siendo una red Docker inspeccionable.
- Separar redes no sustituye una revisión integral de seguridad.
Mini reto: dos redes
Antes de modificar la definición para el reto, cierra la práctica guiada. Mientras compose.yaml todavía contiene web, app y db, ejecuta:
docker compose down
Comprueba que sus contenedores y redes se han eliminado. Ahora puedes cambiar la definición y comenzar el mini reto con web, api y db, sin conservar el antiguo contenedor de app.
Construye sin container_name:
web ── publica ── api ── datos ── db
- Define
web,apiydb. - Publica solo el acceso necesario desde el host.
- Conecta
websolo apublica,apia ambas ydbsolo adatos. - Configura
apipara usardb:5432. - Ejecuta
docker compose configy levanta el proyecto. - Comprueba desde
web, connc, queapi:3000acepta conexiones. - Comprueba desde
api, con Node.js, resolución y TCP haciadb:5432. - Intenta conectar desde
webadb:5432connc: debe fallar. Explica por qué no tienen conectividad directa al no compartir red. - Inspecciona
publicaydatosy demuestra sus membresías. - Cambia temporalmente el host de la prueba TCP desde
apialocalhost, recoge el error, restáuralo adby comprueba que vuelve a conectar. - Explica por qué no has publicado PostgreSQL.
- Limpia solo este proyecto con
docker compose downy comprueba que sus contenedores y redes se han eliminado.
La siguiente unidad añadirá persistencia. Aquí no hemos declarado volúmenes ni afirmado que el orden de arranque garantice disponibilidad.