Contenido

Tutorial de redes en Docker: bridge, redes personalizadas y comunicación entre contenedores

Cuando una aplicación necesita conectarse a otros contenedores, como una base de datos o una caché, necesitas entender las redes de Docker. La idea clave es esta: si pones en la misma red personalizada los contenedores que necesitan comunicarse, podrán conectarse entre sí usando sus nombres.

Diagrama de una red personalizada de Docker y la asignación de puertos
El navegador accede a app a través del puerto de la máquina host asignado con -p; app se conecta a la base de datos con el nombre db dentro de la red personalizada, sin necesidad de exponer la base de datos al exterior

Controladores de red

Una instalación nueva de Docker incluye tres redes integradas:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
a74caef4acb9   bridge    bridge    local
b9a49b39804b   host      host      local
87a8a7fd2cd9   none      null      local
ControladorDescripción
bridgeValor predeterminado. El contenedor tiene su propia IP dentro de una subred virtual
hostEl contenedor usa directamente la red de la máquina host, sin aislamiento (funciona mejor en Linux)
noneSin red
overlayAbarca varias máquinas host (Docker Swarm)
macvlanHace que el contenedor aparezca en la red local como si fuera una máquina física

Limitaciones de la red bridge predeterminada

Si no especificas una red, el contenedor se conecta a la red predeterminada llamada bridge. En esta red, los contenedores no pueden conectarse usando sus nombres:

$ docker run -d --name web1 nginx:1.30
b1d3fb9024c112c5990d9ac85012cd3f5e38c114ab4409a06f960c6d8cdb496e

$ docker run --rm alpine:3.23 ping -c 1 -W 2 web1
ping: bad address 'web1'

$ docker inspect --format '{{.NetworkSettings.IPAddress}}' web1

template parsing error: template: :1:18: executing "" at <.NetworkSettings.IPAddress>: map has no entry for key "IPAddress"

$ docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web1
172.18.0.2

$ docker run --rm alpine:3.23 ping -c 1 172.18.0.2
PING 172.18.0.2 (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.058 ms

--- 172.18.0.2 ping statistics ---
1 packets transmitted, 1 packets received, 0% packet loss
round-trip min/avg/max = 0.058/0.058/0.058 ms

$ docker rm -f web1
web1
  • No encuentra el nombre web1 (bad address), pero sí puede conectarse a la IP 172.18.0.2.
  • Sin embargo, Docker asigna la IP automáticamente. Puede cambiar al volver a crear el contenedor, así que no debes fijarla en la configuración.

Cambio en Docker 29: En versiones anteriores, {{.NetworkSettings.IPAddress}} permitía obtener la IP del contenedor. Docker 29 ha eliminado este campo (de ahí el template parsing error anterior). Usa {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} en su lugar.

Por eso, en la práctica casi siempre se crea una red personalizada.

Red personalizada: comunicación por nombre

docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
$ docker network create appnet
fadfeb6028cb78335fa94cfd7536265256a914cc461caae1026b6ebb21e9d7a0

$ docker run -d --name db --network appnet -e POSTGRES_PASSWORD=secret postgres:18
227033e27591e6a1c195a6d63a141aaf4486c6285477792fe3080e1d4deeb2c0

$ docker run --rm --network appnet alpine:3.23 ping -c 2 db
PING db (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.069 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.092 ms

--- db ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.069/0.080/0.092 ms

$ docker run --rm --network appnet -e PGPASSWORD=secret postgres:18 psql -h db -U postgres -c "SELECT version();"
                                                      version
--------------------------------------------------------------------------------------------------------------------
 PostgreSQL 18.6 (Debian 18.6-1.pgdg13+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)


$ docker network inspect appnet --format "{{range .Containers}}{{.Name}} {{.IPv4Address}}{{println}}{{end}}"
db 172.19.0.2/16
  • En appnet, el DNS integrado de Docker resuelve el nombre db a la IP del contenedor de la base de datos.
  • El cliente de PostgreSQL se conectó correctamente con -h db y consultó la versión de la base de datos (PostgreSQL 18.6).
  • El contenedor de la base de datos no usa -p, por lo que la máquina host y el exterior no pueden conectarse. Solo pueden hacerlo los contenedores de la misma red, lo que es más seguro.

La configuración de conexión de la aplicación también usa el nombre del contenedor, por ejemplo:

docker run -d --name app --network appnet -p 3000:3000 \
  -e DATABASE_URL=postgres://postgres:secret@db:5432/postgres \
  myapp

Ten en cuenta lo siguiente:

  • Las conexiones entre contenedores usan el puerto interno del contenedor (5432), independientemente de -p.
  • Solo los servicios a los que necesites acceder desde la máquina host o desde el exterior (como un sitio web) requieren -p.
  • Evita usar nombres de contenedor como web o app, que también son palabras de dominios de nivel superior, como destino de conexión. Funcionan dentro de la misma red, pero las consultas desde fuera de ella pueden resolver a direcciones extrañas y llevarte a conclusiones equivocadas al depurar.

Unir un contenedor a varias redes y usar alias de red

$ docker network create appnet
77fadbc5633854c9e75fd9aa65325d0155ad33f5de453abac7f15f6766402bee

$ docker run -d --name app --network appnet nginx:1.30
25b4c1d92caeef5ca8765daf2f3ad6247982cacfad4914000ded8a0a455df851

$ docker network create frontend
45a9ab78fddb9f1b47a22b9ce156144286888c42eadc2b4f589986fe1f98cee0

$ docker network connect frontend app

$ docker inspect --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{$v.IPAddress}}{{println}}{{end}}' app
appnet 172.19.0.2
frontend 172.20.0.2


$ docker run -d --name search --network appnet --network-alias es nginx:1.30
43b07713e7ec032d21e37fdf690064b389f798ed26b2715e7ea18c15840647af

$ docker run --rm --network appnet alpine:3.23 getent hosts es
172.19.0.3        es  es

$ docker rm -f app search
app
search

$ docker network rm appnet frontend
appnet
frontend
  • docker network connect añade un contenedor en ejecución a otra red. Ahora app tiene dos IP. Esto se suele usar para aislar servicios: coloca Nginx y la aplicación en la red frontend, y la aplicación y la base de datos en la red backend; así, Nginx no puede conectarse a la base de datos.
  • --network-alias es permite que otros contenedores de la misma red se conecten a search usando el nombre es. Si varios contenedores usan el mismo alias, DNS devuelve varias IP.

Administrar redes

docker network create appnet
docker network ls
docker network inspect appnet               # Ver los contenedores y las IP
docker network connect appnet nombre_del_contenedor       # Añadir un contenedor en ejecución a la red
docker network disconnect appnet nombre_del_contenedor
docker network rm appnet
docker network prune                        # Eliminar las redes que no se usan

Asignación de puertos: la aplicación debe escuchar en 0.0.0.0

-p 8000:8000 hace que Docker reenvíe al puerto 8000 de la interfaz de red del contenedor el tráfico que llega al puerto 8000 del host. Por eso, la aplicación dentro del contenedor debe escuchar en 0.0.0.0 (todas las interfaces); no puede escuchar solo en 127.0.0.1 (el localhost del propio contenedor). Compara el resultado con el servidor web integrado de Python:

$ docker run -d --name localonly -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 127.0.0.1
246edb96a9affefaab188ac7d5ceec2fb103b511bdac352cca6ed3a80ae6764a

$ curl -sS http://localhost:8000
curl: (52) Empty reply from server

$ docker rm -f localonly
localonly

$ docker run -d --name allif -p 8000:8000 python:3.14-slim python -m http.server 8000 --bind 0.0.0.0
438fa44edf6aec790a7e6b2bfcdb7af33c16a14c661c50b8d9564ff52844e5e6

$ curl -s http://localhost:8000 | head -n 3
<!DOCTYPE HTML>
<html lang="en">
<head>

$ docker rm -f allif
allif
  • Si solo escucha en 127.0.0.1, aunque el puerto esté asignado, curl devuelve Empty reply from server: el tráfico llega al contenedor, pero la aplicación solo espera conexiones en el localhost del propio contenedor y no las recibe.
  • Al cambiar a 0.0.0.0, funciona correctamente.

Muchos servidores de desarrollo escuchan en localhost de forma predeterminada. Cuando los ejecutes en un contenedor, especifica la dirección:

flask run --host 0.0.0.0
uvicorn main:app --host 0.0.0.0
vite --host 0.0.0.0

Conectarse al host desde un contenedor

Para que un contenedor acceda a un servicio del host (por ejemplo, una base de datos que se ejecuta en el host), usa el nombre especial host.docker.internal:

$ docker run --rm alpine:3.23 getent hosts host.docker.internal

$ docker run --rm --add-host=host.docker.internal:host-gateway alpine:3.23 getent hosts host.docker.internal
172.18.0.1        host.docker.internal  host.docker.internal
  • Docker Desktop (Windows, macOS): admite host.docker.internal de forma integrada.
  • Linux: este nombre no está disponible de forma predeterminada (el primer comando no muestra nada). Añade --add-host=host.docker.internal:host-gateway; así se resolverá a la dirección del host en la red de Docker.

Los servicios del host también deben escuchar en una interfaz a la que pueda conectarse el contenedor, y no solo en 127.0.0.1.

Red host y red none

docker run -d --network host nginx:1.30

El contenedor usa directamente la interfaz de red del host. Si nginx escucha en el puerto 80, será el puerto 80 del host; no hace falta -p (si lo escribes, se ignorará). Ofrece el mejor rendimiento, pero elimina el aislamiento y puede competir por puertos con las aplicaciones del host. Su comportamiento en Docker Desktop no es exactamente igual que en Linux.

--network none deja el contenedor sin red, salvo la interfaz de loopback local:

$ docker run --rm --network none alpine:3.23 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever

Es útil para tareas que no necesitan red o no deben conectarse a ella, como procesar archivos que no son de confianza.

Solución de problemas

Los contenedores no se conectan entre sí mediante localhost

Cada contenedor tiene su propio localhost. Si desde el contenedor app te conectas a localhost:5432, te conectas al propio app, no al contenedor de la base de datos. Usa el nombre del contenedor, por ejemplo, db:5432.

El host no puede conectarse al contenedor

Comprueba lo siguiente en orden:

  1. Ejecuta docker ps y comprueba si el campo PORTS muestra 0.0.0.0:8080->80/tcp.
  2. Comprueba si la aplicación del contenedor escucha en 0.0.0.0 (el mensaje Empty reply from server anterior es un síntoma típico).
  3. Comprueba si el firewall del host o el grupo de seguridad de la nube permite ese puerto.

El contenedor no puede conectarse a Internet

Suele ser un problema de DNS. Puedes especificar los DNS en /etc/docker/daemon.json (en Docker Desktop, ve a Settings → Docker Engine). Después, reinicia Docker:

{ "dns": ["1.1.1.1", "8.8.8.8"] }

Al usar Docker Compose, Compose crea automáticamente una red para el proyecto, y los servicios pueden conectarse entre sí directamente usando sus nombres.

Resumen

  • La red bridge predeterminada no proporciona resolución de nombres; usa una red personalizada para conectar contenedores entre sí.
  • En una red personalizada, conecta los contenedores mediante sus nombres o --network-alias.
  • La aplicación debe escuchar en 0.0.0.0 para que funcione la asignación de puertos.
  • Para que un contenedor se conecte al host, usa host.docker.internal.

Ejercicios

Ejercicio 1: crea la red lab, inicia un contenedor redis:8-alpine llamado cache y añádelo a esa red. Después, prueba la conexión desde otro contenedor con redis-cli -h cache ping.

Respuesta
docker network create lab
docker run -d --name cache --network lab redis:8-alpine
docker run --rm --network lab redis:8-alpine redis-cli -h cache ping

La salida es PONG. Consulta los comandos de red en docker network.

Preguntas frecuentes

¿Por qué localhost dentro de un contenedor no conecta con otro contenedor?

Cada contenedor tiene su propia red, y localhost apunta al propio contenedor. Para conectar con otro contenedor, pon ambos en la misma red personalizada y conéctate usando el nombre del contenedor.

¿Cómo se conecta un contenedor a un servicio que se ejecuta en la máquina host?

En Docker Desktop, usa host.docker.internal; en Linux, añade --add-host host.docker.internal:host-gateway. Consulta «Conectar un contenedor con la máquina host» en este capítulo.

¿Cómo obtengo la IP de un contenedor en Docker 29?

Se ha eliminado {{.NetworkSettings.IPAddress}}; usa docker inspect -f "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}" contenedor.

Actualizado 14 de octubre de 2026