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
sudoy añadido al grupodocker. - Docker Engine instalado desde el repositorio oficial de Docker (paquetes
docker-ceydocker-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
| Controlador | Alcance | Descubrimiento por nombre | Uso típico |
|---|---|---|---|
bridge (predeterminado) | Un host | No | Contenedores sueltos sin configurar |
bridge definido por el usuario | Un host | Sí | Aplicaciones de un solo servidor (el caso más común) |
host | Un host | No aplica | Máximo rendimiento o acceso directo a interfaces |
none | Un host | No aplica | Tareas sin red, aislamiento total |
overlay | Varios hosts | Sí | Docker Swarm, servicios repartidos entre nodos |
macvlan | Red física | No | El 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
AdvertenciaDocker añade sus propias reglas de iptables para los puertos publicados y estas se evalúan antes que las de UFW. Un puerto publicado con
-p 8080:80queda abierto a Internet aunque UFW lo bloquee.
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.
ImportanteLa mayoría de proveedores cloud, incluidos los VPS, filtran las direcciones MAC que no conocen, por lo que macvlan normalmente solo funciona en servidores dedicados o en tu propia red local. Además, por diseño, el host no puede comunicarse directamente con sus contenedores macvlan.
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.
