Por qué dos contenedores en el mismo host no se ven por defecto

Levanta Postgres y tu API con un simple docker run y la API no puede llegar a la base de datos, aunque los dos contenedores estén en la misma máquina.

1
2
docker run -d --name db postgres:16
docker run -d --name api myapp

Los dos caen en la bridge por defecto de Docker, la que recibe todo contenedor Docker que no diga lo contrario. Reciben una IP. Lo que no reciben es una forma de encontrarse por nombre, así que la API tendría que llevar la IP interna de la base de datos escrita a mano, una dirección que cambia cada vez que el contenedor se reinicia.

Una red Docker resuelve esto. Es un switch virtual al que se conectan los contenedores, con su propio rango de IPs y su propio DNS. Al crear una, los nombres de los contenedores pasan a ser hostnames y decides tú quién ve a quién.

Qué driver de red usar: bridge, host u overlay

Docker incluye varios drivers de red integrados. Tres de ellos cubren casi todo lo que te vas a encontrar en un solo host o en un swarm:

DriverAlcanceLos contenedores tienen IP propiaDNS por nombreUso típico
bridge (por defecto)un solo hostno (solo IP)lo que obtienes sin el flag --network
bridge (user-defined)un solo hostla opción por defecto para desarrollo local y despliegues de un solo host
hostun solo hostno, comparte la pila de red del hostn/aservicios de alto throughput, herramientas que necesitan ver la red real del host
overlayvarios hosts (swarm)servicios repartidos entre más de un host Docker

También existe none, que le da al contenedor solo una interfaz de loopback y nada más. Útil para un trabajo batch que no tiene nada que hacer en la red.

Cada docker run nuevo aterriza en la bridge por defecto. Funciona, pero solo enruta por IP; el viejo flag --link existía para tapar la falta de nombres, y Docker lo marca como legacy. Una bridge user-defined hace el mismo trabajo con DNS incluido. Esa es la que quieres.

Cómo crear una red Docker y conectarle contenedores

1
docker network create app-net

Eso es una bridge privada con su propia subred, aislada de la bridge por defecto y de cualquier otra red user-defined que crees. Conecta los contenedores al arrancarlos:

1
2
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=devpass postgres:16
docker run -d --name api --network app-net -p 8080:8080 myapp

Dentro de api, conectarse a db:5432 funciona. Sin buscar IPs, sin hacer malabares con variables de entorno: el DNS integrado de Docker resuelve el nombre del contenedor db a su dirección actual en app-net, y actualiza esa relación cuando el contenedor se reinicia con una IP nueva. Docker Compose funciona sobre el mismo mecanismo. docker compose up crea una red para el stack, y cada servicio llega a los demás por su nombre de servicio.

También puedes conectar un contenedor que ya está corriendo, sin reiniciarlo:

1
2
docker network connect app-net some-container
docker network disconnect app-net some-container

Un contenedor puede estar en más de una red a la vez. Así es como le das a un servicio una red expuesta al público y otra privada compartida solo con los contenedores de backend que hay detrás.

Por qué publicar un puerto no es cómo dos contenedores se comunican

-p 8080:8080 mapea el puerto 8080 del host al del contenedor, así que puedes llegar a la API desde el navegador en localhost:8080. No dice nada sobre si api puede llegar a db. El tráfico entre contenedores de la misma red user-defined se queda interno, publiques puertos o no.

Publica solo los puertos que algo fuera del host Docker necesita alcanzar. Una base de datos que solo habla con la API no necesita -p 5432:5432: déjala sin publicar y ni el host ni internet podrán conectarse a ella directamente. api la sigue alcanzando igual, porque comparten app-net.

Cuándo usar –network host, y cuándo no

1
docker run --rm -d --network host --name my_nginx nginx

Con --network host el contenedor se salta su propio namespace de red y se conecta directamente a las interfaces del host. Sin NAT, sin bridge, sin mapeo de puertos: -p y -P se ignoran, y Docker muestra un aviso si los pasas igualmente. Eso reduce algo de overhead por paquete y le da al contenedor la vista real de las interfaces de red del host, que es lo que buscan las herramientas de monitorización de red y el software que necesita un rango amplio y contiguo de puertos.

También elimina el aislamiento que da una bridge. El contenedor puede usar cualquier puerto libre del host, y ve todo lo que pasa por la red del host. En Linux funciona sin restricciones. Docker Desktop para Mac y Windows requiere activarlo explícitamente (disponible desde Docker Desktop 4.34), porque la red de la VM de Docker no es la del host. Los contenedores Windows no lo soportan en absoluto. Para la mayoría de los servicios, una bridge user-defined con puertos publicados hace el mismo trabajo con menos exposición.

Cuándo necesitas una red overlay en vez de una bridge

Las redes bridge y host se detienen en el borde de un solo host Docker. Corre Docker Swarm en varias máquinas y un servicio en el host A necesita una red overlay para llegar a un servicio en el host B:

1
docker network create -d overlay --attachable app-overlay

Una overlay encapsula el tráfico de los contenedores y lo enruta entre los daemons de Docker de cada nodo del swarm, así que los contenedores en máquinas físicas o virtuales distintas se encuentran por nombre exactamente igual que en una bridge. El flag --attachable permite que también se conecten contenedores independientes; sin él, solo pueden hacerlo los servicios de swarm. Si no usas swarm ni otro orquestador multi-host, sáltate este driver. Una red bridge de un solo host cubre los stacks de Compose y los entornos locales.

Cómo inspeccionar y depurar una red Docker

1
docker network ls

Todas las redes del host, incluidas bridge, host y none, que Docker crea siempre.

1
docker network inspect app-net

La subred, la puerta de enlace y cada contenedor conectado en ese momento con su IP en esa red. Es el primer sitio donde mirar cuando un contenedor no puede llegar a otro. Lee la sección Containers: si falta alguno, no está en esta red, y docker network connect lo pone ahí.

Para probar la resolución DNS desde dentro de un contenedor en ejecución:

1
docker exec api getent hosts db

Que devuelva una IP significa que la resolución de nombres funciona y el problema está en la aplicación: puerto equivocado, o el servicio todavía no escucha. Que no devuelva nada significa que los dos contenedores no están en la misma red. Confírmalo con docker network inspect, o pregúntale a cada contenedor a qué está conectado:

1
docker inspect <container> --format '{{ .NetworkSettings.Networks }}'

Una red por cada stack, con todos sus contenedores dentro

La bridge por defecto es la que te toca gratis, pero no es la red sobre la que deberías construir tus stacks. Ejecuta docker network create <app>-net para cada stack de aplicación, conecta ahí todos los contenedores que ese stack necesite, y reserva --network host para el servicio raro que de verdad necesita la pila de red del host.

Cuando llevas más de un par de contenedores a mano, deja de hacerlo a mano. Docker Compose crea la red y conecta los contenedores por ti desde un único archivo YAML. Los volúmenes Docker cubren la otra mitad de sobrevivir a un reinicio: los datos que esos contenedores escriben.

Artículos relacionados