Listado de la etiqueta: port-mapping

Redes en Docker: de ‘no puedo conectar mis contenedores’ a entenderlo de verdad

Pasé casi tres horas intentando que un contenedor de frontend se comunicara con un contenedor de backend. Hacía curl http://localhost:3000 desde dentro del frontend y no llegaba nada. El problema no era el código: era que no entendía cómo funcionan las redes en Docker.

El modelo de red de Docker

Cada contenedor tiene su propia interfaz de red virtual y su propia dirección IP dentro de la red Docker. El localhost dentro de un contenedor es ese contenedor, no el host ni otro contenedor. Ese fue mi error: intentar llegar a otro contenedor como si fuera mi máquina.

Los tres drivers de red nativos

┌─────────────────────────────────────────────────────────────────┐
│  BRIDGE (default)                                               │
│                                                                 │
│  Host ──── docker0 ──── contenedor1 (172.17.0.2)               │
│                    └─── contenedor2 (172.17.0.3)               │
│                                                                 │
│  • Red virtual privada                                          │
│  • Contenedores aislados entre sí por defecto                  │
│  • Se exponen puertos explícitamente con -p                     │
├─────────────────────────────────────────────────────────────────┤
│  HOST                                                           │
│                                                                 │
│  Host ──── contenedor (comparte la red del host)               │
│                                                                 │
│  • Sin aislamiento de red                                       │
│  • Máximo rendimiento (sin NAT)                                 │
│  • Solo disponible en Linux                                     │
├─────────────────────────────────────────────────────────────────┤
│  NONE                                                           │
│                                                                 │
│  contenedor (sin red - solo loopback)                          │
│                                                                 │
│  • Aislamiento total                                            │
│  • Para procesos que no necesitan red                           │
└─────────────────────────────────────────────────────────────────┘

La solución: redes definidas por el usuario

La red bridge por defecto tiene una limitación importante: los contenedores no se pueden resolver por nombre, solo por IP. Las redes personalizadas solucionan esto con DNS automático. Esta es la forma correcta de conectar contenedores.

# Crear una red personalizada
docker network create mi-app-network

# Levantar backend
docker run -d   --name backend   --network mi-app-network   -e DB_HOST=postgres   mi-api:latest

# Levantar frontend - puede resolver "backend" por nombre
docker run -d   --name frontend   --network mi-app-network   -p 80:3000   -e API_URL=http://backend:8080   mi-frontend:latest

# Levantar base de datos en la misma red
docker run -d   --name postgres   --network mi-app-network   -v postgres-data:/var/lib/postgresql/data   -e POSTGRES_PASSWORD=secreto   postgres:16-alpine

# Ahora desde frontend podés hacer:
# curl http://backend:8080/api/health  ✅
# La base de datos NO está expuesta al exterior (sin -p)

Publicación de puertos: qué exponés y qué no

Un error común es publicar todos los puertos de todos los servicios. La buena práctica: solo el punto de entrada de tu aplicación (el frontend o la API pública) se expone al host. La base de datos, el cache, los servicios internos — solo accesibles dentro de la red Docker.

# ❌ Exponer todo - superficie de ataque innecesaria
docker run -p 5432:5432 postgres    # DB expuesta al mundo
docker run -p 6379:6379 redis       # Cache expuesta al mundo
docker run -p 8080:8080 mi-api      # API interna expuesta

# ✅ Solo exponer el punto de entrada
docker run -p 80:80 mi-nginx        # Solo el proxy/frontend al exterior
# El resto: en red interna, sin -p

Comandos de diagnóstico de red

# Ver redes existentes
docker network ls

# Inspeccionar una red (ver qué contenedores están conectados)
docker network inspect mi-app-network

# Conectar/desconectar un contenedor de una red en caliente
docker network connect mi-app-network contenedor-existente
docker network disconnect mi-app-network contenedor-existente

# Diagnóstico de conectividad desde dentro de un contenedor
docker exec -it frontend ping backend
docker exec -it frontend curl http://backend:8080/health
docker exec -it frontend nslookup backend  # resolución DNS

Lo que debería haber hecho desde el principio

La solución a mis tres horas de frustración era simple: crear una red personalizada y usar los nombres de contenedor como hostnames. Desde que lo entendí, la comunicación entre servicios es trivial. La clave mental: dentro de una red Docker personalizada, el nombre del contenedor es el hostname. http://backend:8080 funciona igual que http://192.168.1.100:8080, pero sin tener que saber IPs que cambian.


Artículo anterior: Volúmenes y persistencia | Serie Docker Completo | Próximo: Docker Compose →


Artículo anterior: Cuando perdí datos de producción por no usar volúmenes (y cómo no repetirlo) | Serie Docker Completo | Próximo: Docker Compose: el día que dejé de levantar contenedores a mano →