← maurobernal.com.ar

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 →

Tags

tsql (27)mssql (26)sql (20)devops (20)dotnet (18)docker (15)performance (14)contenedores (11)dotnet10 (10)linux (9)csharp (8)microservicios (7)angular (7)angular21 (7)sql server (6)issabel (6)docker-compose (6)typescript (6)mysql (5).NET (5)