Docker conecta los contenedores a la red a través de controladores (drivers): cada uno decide si el contenedor tiene su propia pila de red, comparte la del host o se comunica con contenedores de otros servidores. Elegir bien el controlador determina cómo se descubren los servicios entre sí, qué puertos quedan expuestos y cuánto aislamiento tienes. En esta guía probarás en Ubuntu 24.04 los controladores bridge, host, none, overlay y macvlan con ejemplos que puedes ejecutar tal cual.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo y añadido al grupo docker.
  • Docker Engine instalado desde el repositorio oficial de Docker (paquetes docker-ce y docker-compose-plugin).
  • Para la sección de overlay: un segundo servidor con Docker y conectividad privada entre ambos.

Comprueba que Docker funciona:

docker version --format '{{.Server.Version}}'

Resumen de los controladores de red

ControladorAlcanceDescubrimiento por nombreUso típico
bridge (predeterminado)Un hostNoContenedores sueltos sin configurar
bridge definido por el usuarioUn hostSíAplicaciones de un solo servidor (el caso más común)
hostUn hostNo aplicaMáximo rendimiento o acceso directo a interfaces
noneUn hostNo aplicaTareas sin red, aislamiento total
overlayVarios hostsSíDocker Swarm, servicios repartidos entre nodos
macvlanRed físicaNoEl contenedor necesita IP propia en la LAN

Paso 1: Revisar las redes predeterminadas

Al instalar Docker se crean tres redes. Lístalas:

docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
3f1c2d7a9b10   bridge    bridge    local
8a4e6b2c1d33   host      host      local
c7d9e0f1a2b4   none      null      local

La red bridge corresponde a la interfaz docker0 del host, un switch virtual al que cada contenedor se conecta mediante un par veth. Consulta su subred:

docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}} {{.Gateway}}{{end}}'
172.17.0.0/16 172.17.0.1

Paso 2: Entender las limitaciones del bridge predeterminado

Si no indicas --network, el contenedor se conecta a bridge. Arranca dos contenedores:

docker run -d --name web1 nginx:alpine
docker run -d --name web2 nginx:alpine

Desde web2, intenta llegar a web1 por su nombre:

docker exec web2 ping -c 1 web1
ping: bad address 'web1'

En la red bridge predeterminada no hay DNS entre contenedores: solo se comunican por IP, y la IP cambia al recrear el contenedor. Por eso Docker recomienda no usarla para aplicaciones. Elimina los contenedores de prueba:

docker rm -f web1 web2

Paso 3: Crear una red bridge definida por el usuario

Una red bridge propia añade un servidor DNS interno (en 127.0.0.11 dentro de cada contenedor) que resuelve los nombres de los contenedores conectados a ella. También aísla la aplicación: los contenedores de otras redes no pueden alcanzarla.

Crea la red:

docker network create app-net

Arranca un servidor web en ella y, en la misma red, un contenedor temporal que lo consulte por nombre:

docker run -d --name web --network app-net nginx:alpine
docker run --rm --network app-net alpine wget -qO- http://web
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...

El nombre web se resuelve sin configurar nada. Si necesitas controlar el rango de direcciones (por ejemplo, para evitar solapes con una VPN o una red privada), indícalo al crear la red:

docker network create --subnet 10.30.0.0/24 --gateway 10.30.0.1 backend-net

Puedes asignar una IP fija con --ip a un contenedor de esa red, aunque normalmente es mejor usar el nombre:

docker run -d --name cache --network backend-net --ip 10.30.0.10 redis:7-alpine

Conectar un contenedor a varias redes

Un contenedor puede estar en más de una red. Es el patrón habitual para separar el frontal de la base de datos: el proxy está en la red pública y en la interna, y la base de datos solo en la interna. Conecta web también a backend-net:

docker network connect backend-net web
docker inspect web --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{$v.IPAddress}}{{"\n"}}{{end}}'
app-net 172.18.0.2
backend-net 10.30.0.2

Para desconectarlo, usa docker network disconnect backend-net web.

Crear una red sin salida a Internet

La opción --internal crea una red sin ruta hacia el exterior, útil para bases de datos que solo deben hablar con la aplicación:

docker network create --internal db-net
docker run --rm --network db-net alpine wget -qO- -T 3 http://example.com

El comando falla, con un error de resolución o por tiempo de espera según la versión de Docker, porque la red no tiene salida al exterior. Los contenedores conectados a db-net sí pueden comunicarse entre sí por nombre.

Paso 4: Publicar puertos de forma segura

Los contenedores en una red bridge no son accesibles desde fuera del host hasta que publicas un puerto con -p puerto_host:puerto_contenedor:

docker run -d --name web-public --network app-net -p 8080:80 nginx:alpine
curl -sI http://localhost:8080 | head -n 1
HTTP/1.1 200 OK

Si el servicio solo debe ser accesible desde el propio servidor (por ejemplo, detrás de Nginx en el host), publica el puerto únicamente en localhost:

docker run -d --name web-local --network app-net -p 127.0.0.1:8081:80 nginx:alpine

Comprueba en qué direcciones escucha cada puerto:

sudo ss -tlnp | grep -E ':(8080|8081)\b'
LISTEN 0 4096 127.0.0.1:8081 0.0.0.0:* users:(("docker-proxy",pid=4120,fd=7))
LISTEN 0 4096   0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=3988,fd=7))

Limpia los contenedores de este paso:

docker rm -f web-public web-local

Paso 5: Usar la red host

Con --network host el contenedor no tiene pila de red propia: usa directamente las interfaces y puertos del servidor. No hay NAT ni docker-proxy, así que -p no tiene efecto. Es útil para herramientas que necesitan ver las interfaces reales (monitorización, captura de tráfico) o para servicios muy sensibles a la latencia.

docker run -d --name web-host --network host nginx:alpine
curl -sI http://localhost:80 | head -n 1
HTTP/1.1 200 OK

El inconveniente es que no hay aislamiento de red: el contenedor puede chocar con puertos que ya use el host (si tienes Nginx instalado en el servidor, este contenedor no arrancará) y todo lo que escuche en 0.0.0.0 queda expuesto. Elimínalo:

docker rm -f web-host

Paso 6: Usar la red none

La red none deja al contenedor solo con la interfaz de loopback. Sirve para trabajos que procesan ficheros y no deben tener ningún acceso a la red:

docker run --rm --network none alpine ip -brief addr
lo               UNKNOWN        127.0.0.1/8 ::1/128

Paso 7: Conectar contenedores entre hosts con overlay

Una red overlay une contenedores de varios servidores con un túnel VXLAN, y funciona sobre Docker Swarm. Antes de empezar, permite el tráfico de Swarm solo desde la IP privada del otro nodo en ambos servidores. Sustituye other_node_ip por esa IP:

sudo ufw allow from other_node_ip to any port 2377 proto tcp
sudo ufw allow from other_node_ip to any port 7946
sudo ufw allow from other_node_ip to any port 4789 proto udp

El puerto 2377/tcp es la gestión del clúster, 7946 (TCP y UDP) la comunicación entre nodos y 4789/udp el tráfico VXLAN. En el primer servidor, inicializa Swarm anunciando su IP privada, manager_private_ip:

docker swarm init --advertise-addr manager_private_ip

La salida incluye un comando docker swarm join --token .... Ejecútalo en el segundo servidor y comprueba desde el manager que ambos nodos están listos:

docker node ls
ID                            HOSTNAME   STATUS    AVAILABILITY   MANAGER STATUS   ENGINE VERSION
k2x8v1c9q0m3n4b5v6c7x8z9a *   node-1     Ready     Active         Leader           28.4.0
p9o8i7u6y5t4r3e2w1q0a1s2d     node-2     Ready     Active                          28.4.0

Crea la red overlay. --attachable permite conectar también contenedores sueltos creados con docker run, no solo servicios:

docker network create --driver overlay --attachable app-overlay

Despliega un servicio con tres réplicas en esa red:

docker service create --name web --network app-overlay --replicas 3 nginx:alpine
docker service ps web --format '{{.Name}} {{.Node}} {{.CurrentState}}'
web.1 node-1 Running 20 seconds ago
web.2 node-2 Running 20 seconds ago
web.3 node-1 Running 20 seconds ago

Desde cualquier contenedor de la red, el nombre del servicio resuelve a una IP virtual que reparte las peticiones entre las réplicas, estén en el nodo que estén:

docker run --rm --network app-overlay alpine wget -qO- http://web | grep -o '<title>.*</title>'
<title>Welcome to nginx!</title>

Para cifrar el tráfico entre nodos, crea la red con --opt encrypted. Docker usa IPsec (protocolo ESP), así que el firewall debe permitir ese protocolo entre los nodos, y el cifrado tiene un coste de rendimiento.

Paso 8: Dar a un contenedor una IP de la LAN con macvlan

El controlador macvlan asigna al contenedor su propia dirección MAC e IP dentro de la red física, como si fuera otra máquina conectada al switch. Es útil para aplicaciones heredadas que esperan estar directamente en la LAN.

Sustituye la subred, la puerta de enlace y la interfaz (eth0) por las de tu red, y reserva un rango de IP que tu servidor DHCP no reparta:

docker network create --driver macvlan \
  --subnet 192.168.1.0/24 \
  --gateway 192.168.1.1 \
  --ip-range 192.168.1.240/28 \
  -o parent=eth0 \
  lan-net
docker run -d --name legacy-app --network lan-net --ip 192.168.1.240 nginx:alpine

Desde otro equipo de la LAN (no desde el propio host), http://192.168.1.240 debe responder.

Paso 9: Definir redes en Docker Compose

En la práctica, las redes se declaran en compose.yaml. Compose crea una red bridge por proyecto automáticamente, pero declararlas permite separar capas. Crea un proyecto de ejemplo:

mkdir -p ~/compose-redes && cd ~/compose-redes
nano compose.yaml
services:
  proxy:
    image: nginx:alpine
    ports:
      - "8080:80"
    networks:
      - frontend
      - backend

  cache:
    image: redis:7-alpine
    networks:
      - backend

networks:
  frontend:
  backend:
    internal: true

Aquí proxy está en ambas redes y publica el puerto 8080, mientras que cache solo es accesible desde la red backend, que no tiene salida a Internet. Arranca el proyecto y comprueba que proxy resuelve cache:

docker compose up -d
docker compose exec proxy ping -c 1 cache
PING cache (172.21.0.2): 56 data bytes
64 bytes from 172.21.0.2: seq=0 ttl=64 time=0.091 ms

Compose antepone el nombre del proyecto a las redes (compose-redes_backend). Detén el proyecto con docker compose down, que también elimina sus redes.

Solución de problemas

Un contenedor no resuelve el nombre de otro

Comprueba que ambos están en la misma red definida por el usuario, no en el bridge predeterminado:

docker inspect web --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}} {{end}}'

La red de Docker choca con una red privada o VPN

Si pierdes acceso a una red interna al crear redes de Docker, es porque Docker ha tomado un rango que ya usas. Define otros rangos predeterminados en /etc/docker/daemon.json:

sudo nano /etc/docker/daemon.json
{
  "default-address-pools": [
    { "base": "10.200.0.0/16", "size": 24 }
  ]
}

Reinicia Docker para aplicarlo. Solo afecta a las redes nuevas, así que recrea las existentes:

sudo systemctl restart docker

Depurar la red desde dentro de un contenedor

La imagen nicolaka/netshoot incluye dig, curl, tcpdump, ss y otras herramientas. Puedes unirla al espacio de nombres de red de un contenedor existente para ver exactamente lo que él ve:

docker run --rm -it --network container:web nicolaka/netshoot

Dentro, dig web o ss -tlnp muestran la resolución DNS y los puertos desde el punto de vista del contenedor.

No se puede eliminar una red

docker network rm falla si algún contenedor sigue conectado. Localízalos:

docker network inspect app-net --format '{{range .Containers}}{{.Name}} {{end}}'

Conclusión

Has visto por qué conviene usar redes bridge definidas por el usuario en lugar de la predeterminada, cómo publicar puertos sin exponerlos de más, cuándo usar host o none, y cómo unir varios servidores con una red overlay de Swarm o dar IP de la LAN a un contenedor con macvlan. Como siguientes pasos, puedes limpiar los recursos de prueba con docker network prune, poner un proxy inverso como Nginx o Traefik delante de tus contenedores, o estudiar cómo Kubernetes resuelve el mismo problema con plugins CNI.