Algunos incidentes no empiezan con un fallo evidente. Empiezan con “Solo cambiamos la red”. Y de repente tus contenedores no pueden alcanzar la base de datos, tu monitorización queda ciega y el equipo de seguridad descubre que tu app ahora escucha en todas las interfaces como si fuera 2009.
Docker te ofrece tres palancas tentadoras para el comportamiento L2/L3 local—bridge, host y macvlan. Las tres pueden funcionar. Las tres pueden arruinarte el fin de semana si las eliges por la razón equivocada. Elijamos la que envejece bien en producción.
Decisión primero: qué elegir en 60 segundos
Si gestionas sistemas en producción, tu valor por defecto debería ser aburrido. Las redes aburridas generan menos alertas.
Elige bridge cuando
- Necesitas publicación de puertos (
-p) y aislamiento razonable. - Quieres que el host siga siendo el host, no “sopa de contenedores con root arriba”.
- Tienes múltiples servicios por host y quieres que coexistan sin conflictos de puertos.
- Quieres un camino hacia diseños más avanzados después (múltiples redes, redes internas, controles de política).
Elige host cuando
- Necesitas bajo overhead y puedes aceptar el radio de impacto (filtros de paquetes, puertos y namespaces se comparten).
- Ejecutas un servicio principal orientado a la red por host, y ya enlaza los puertos que necesita.
- Tienes una buena estrategia para firewall y observabilidad en el host.
Elige macvlan cuando
- Necesitas que los contenedores aparezcan como ciudadanos L2 de primera clase con su propia MAC/IP en tu LAN física.
- Te integras con sistemas que esperan IPs únicas por workload (licencias legadas, listas de control basadas en IP, routers upstream, multicast, monitorización antigua, “appliances de seguridad”).
- Puedes gestionar las realidades de L2: ARP, tablas CAM, seguridad de puertos del switch y disciplina de IPAM.
Mi valor por defecto y con sesgo: comienza con redes bridge definidas por el usuario. Usa host solo cuando puedas defender el riesgo por escrito. Usa macvlan solo cuando debas integrarte con la LAN como un host real y hayas validado que el switch no te castigará por ello.
El modelo mental que evita incidentes tontos
El networking de Docker no es magia. Es namespaces de red de Linux más algo de pegamento: pares de Ethernet virtual (veth), un dispositivo bridge, reglas de enrutamiento y reglas de NAT/iptables/nftables. El “driver” que eliges decide mayormente por dónde viajan esos paquetes y quién posee los puertos.
Tres preguntas que lo deciden todo
- ¿Dónde viven los puertos? ¿Mapeas puertos del contenedor a puertos del host (bridge), o el contenedor comparte el espacio de puertos del host (host), o el contenedor obtiene su propia IP (macvlan)?
- ¿Quién hace la aplicación de políticas L3/L4? ¿Firewall del host + reglas gestionadas por Docker, o ACLs de la red upstream, o ambos?
- ¿Cuál es tu dominio de fallo? ¿Quieres que “un contenedor se volvió raro” se convierta en “el host está raro”?
Además: el rendimiento rara vez es tu primer problema. La capacidad de depuración y la previsibilidad sí lo son. Un driver de red que sea 3% más rápido pero 30% más difícil de diagnosticar no es una optimización. Es un incidente futuro con una invitación de calendario.
Una cita que he visto hacerse realidad en más postmortems de los que quisiera contar: idea parafraseada: “La esperanza no es una estrategia.”
— Gene Kranz (idea parafraseada)
Networking bridge: el predeterminado con razón
El modo bridge es la historia de Docker “quiero que los contenedores sean su propio pequeño mundo, pero aún accesibles”. El contenedor obtiene una IP en una subred privada. Docker crea un bridge de Linux (como docker0 o uno definido por el usuario), luego conecta el eth0 del contenedor a él mediante un par veth. El tráfico saliente se enruta/NATea hacia la interfaz del host. El tráfico entrante suele usar puertos publicados.
Por qué bridge es apto para producción
- La publicación de puertos es explícita. Abres solo lo que quieres abrir. Eso importa cuando la imagen del contenedor cambia y de repente enlaza puertos adicionales.
- Los nombres importan. Las redes bridge definidas por el usuario ofrecen descubrimiento de servicios basado en DNS integrado. Los contenedores pueden hablar por nombre sin que pegues IPs en configuraciones.
- El aislamiento es real, más o menos. No es un límite de VM completo, pero es una línea de contención significativa para colisiones accidentales de puertos y algunas clases de mala configuración.
- La depuración es manejable. Puedes razonar sobre los flujos: contenedor → veth → bridge → host → uplink; y las reglas de NAT son visibles.
Cuando bridge puede causar problemas
- Desajustes de MTU. Las redes overlay, VPNs o tramas jumbo pueden hacer que PMTUD falle y los paquetes desaparezcan.
- Sorpresas de NAT. El cambio de IP de origen puede romper ACLs upstream o confundir logs.
- Comportamiento hairpin. Contenedor→host→contenedor vía puerto publicado puede comportarse diferente que el tráfico directo contenedor→contenedor.
- Deriva del firewall. Docker manipula iptables/nftables. Si tu firewall base asume control total, habrá una guerra de territorios.
El networking bridge es como un sedán fiable. No es sexy, pero arranca en invierno, las piezas son baratas y todo el mundo sabe cómo arreglarlo.
Networking host: rápido, afilado e inseguro por defecto
--network host deja las apariencias: el contenedor comparte el namespace de red del host. Sin NAT. Sin IP de contenedor. Sin publicación de puertos. Si el proceso enlaza 0.0.0.0:443, está enlazando el puerto 443 del host. Ese es el punto.
Para qué es realmente bueno el modo host
- Workloads de alto ritmo de paquetes donde NAT y el overhead de conntrack son medibles y problemáticos.
- Appliances de red (demonios de enrutamiento, speakers BGP, servidores DHCP) donde quieres semánticas de interfaz directas.
- Hosts simples de un solo inquilino donde un workload posee la máquina.
Qué rompe el modo host (silenciosamente)
- Las colisiones de puertos se vuelven fallos “aleatorios”. Despliega dos servicios que quieran 8125/udp y lo descubrirás en tiempo de ejecución.
- Los límites de seguridad se desdibujan. El contenedor puede ver interfaces del host, a veces servicios locales del host, y tu suposición de “está solo dentro de Docker” desaparece.
- La observabilidad se complica. Herramientas que esperan IPs de contenedor pierden un ancla; la atribución de tráfico puede requerir herramientas conscientes de cgroups.
Broma corta #1: El networking host es como darle a tu contenedor las llaves maestras porque prometió que solo iba a mover el coche para lavarlo.
Gobernanza del modo host que lo hace viable
- Usa systemd o un orquestador para evitar que dos contenedores enlacen el mismo puerto.
- Aplica la política de firewall del host explícitamente; no confíes en los “buenos valores por defecto” de Docker.
- Documenta la propiedad de puertos por host como si fuera un contrato. Porque lo es.
- Prefiere el modo host solo para workloads que lo justifiquen: captura de paquetes, agentes de métricas, proxies de borde o demonios de red reales.
Macvlan: la opción que “parece un host real” (y sus trampas)
Macvlan asigna a cada contenedor su propia dirección MAC e IP en tu segmento de red físico. Desde el resto de la LAN, el contenedor es un par. Sin mapeo de puertos, sin NAT. Simplemente “aquí hay otra máquina”. Es seductor porque elimina una categoría de incomodidades: los sistemas upstream pueden hablar directamente con los contenedores sin malabarismos de puertos.
Cuando macvlan es la respuesta correcta
- ACLs legadas y listas de permitidos basadas en IP donde no puedes o no quieres reescribir la política alrededor de NAT.
- Contenedores tipo appliance que necesitan su propia identidad IP para enrutamiento, monitorización o segmentación de red.
- Software dependiente de multicast/broadcast donde las semánticas de NAT/bridge son problemáticas (con matices; no todo se vuelve más fácil).
La gran trampa de macvlan: tráfico host→contenedor
Por defecto, con macvlan, el host no puede hablar con sus hijos macvlan en la misma interfaz física. Esto sorprende a la gente cada vez. Los paquetes no hacen hairpin como esperarías.
La solución suele ser crear una subinterfaz macvlan en el host (una interfaz “shim”) en la misma red macvlan y enrutar a través de ella. No es difícil, pero es otra parte móvil que debes mantener a través de reinicios y gestión de configuración.
Dónde macvlan duele después
- Seguridad de puertos del switch y límites de tabla CAM. Algunos switches no aceptan que un puerto físico emita de repente docenas de MACs. Lo aprenderás a las 2 a.m., durante un incidente, si no lo preguntas antes.
- Tormentas ARP y churn de tablas de vecinos. Los contenedores aparecen y desaparecen; las caches ARP no siempre se mantienen al día.
- IPAM se convierte en tu problema. El IPAM de Docker puede asignar desde un rango, pero no va a negociar con tu servidor DHCP o la hoja de cálculo del equipo de red a menos que hagas el trabajo.
- Pruebas “en una sola máquina” más difíciles. No puedes ejecutar todo en un portátil y esperar que la LAN se comporte igual, especialmente cuando entra Wi‑Fi en la ecuación.
Broma corta #2: Macvlan es genial hasta que tu switch ve treinta nuevas MACs y decide practicar la atención plena dejando caer el tráfico.
Hechos interesantes y un poco de historia (útil, no para trivial)
- Namespaces de red de Linux (la base del networking de contenedores) llegaron al kernel a finales de los 2000, originalmente para aislar pilas de red para procesos sin virtualización completa.
- El networking temprano de Docker se apoyaba mucho en reglas
iptablesde NAT; durante años, “Docker rompió mi firewall” fue un rito de iniciación porque insertaba cadenas automáticamente. - Las redes bridge definidas por el usuario añadieron descubrimiento de servicios basado en DNS embebido, que fue un gran avance respecto al antiguo mecanismo
--linkque incrustaba entradas de host frágiles en los contenedores. - Macvlan como característica del kernel es anterior a la adopción por Docker; es un driver de Linux que permite que múltiples MAC virtuales compartan una interfaz física, usado históricamente para segregación de red y entornos de laboratorio.
- Conntrack (seguimiento de conexiones) es un impuesto oculto en configuraciones con mucho NAT; tasas altas de conexiones pueden agotar las tablas conntrack y parecer pérdida de paquetes aleatoria.
- Los problemas de MTU empeoraron con la popularidad de overlays/VPNs; “funciona en un host, falla entre sitios” a menudo se reduce a path MTU y fragmentación.
- El networking host es efectivamente optar por salir de uno de los aislamientos más prácticos de los contenedores: la separación de namespaces de puertos. Por eso muchos orquestadores lo tratan como una opción privilegiada.
- Bridge vs macvlan suele ser un debate sobre dónde vive la identidad: en el host (bridge/NAT) o en la LAN (macvlan). Ninguna es gratuita.
Tareas prácticas: comandos, salidas y la decisión que tomas
Estas son las tareas que realmente ejecuto cuando alguien dice: “La red está rara”. Cada una tiene un comando, una salida de ejemplo, lo que significa y qué decides después.
Tarea 1: Lista las redes Docker y detecta lo obvio
cr0x@server:~$ docker network ls
NETWORK ID NAME DRIVER SCOPE
a1b2c3d4e5f6 bridge bridge local
f6e5d4c3b2a1 host host local
112233445566 none null local
77aa88bb99cc app-net bridge local
Qué significa: Tienes el bridge por defecto, más un bridge definido por usuario (app-net). Eso es bueno: las redes bridge definidas por usuario dan mejor comportamiento DNS y separación.
Decisión: Si tus servicios siguen usando el bridge predeterminado y patrones legados, muévelos a una red definida por usuario salvo que haya una razón para no hacerlo.
Tarea 2: Inspecciona una red y confirma subred, gateway y opciones
cr0x@server:~$ docker network inspect app-net
[
{
"Name": "app-net",
"Driver": "bridge",
"IPAM": {
"Config": [
{
"Subnet": "172.22.0.0/16",
"Gateway": "172.22.0.1"
}
]
},
"Options": {
"com.docker.network.bridge.name": "br-77aa88bb99cc"
}
}
]
Qué significa: Este bridge usa un dispositivo bridge de Linux dedicado y una subred definida. Predecible.
Decisión: Si esta subred se solapa con tu VPN corporativa o rangos del centro de datos, cámbiala ahora. El solapamiento causa incidentes “solo roto desde algunos portátiles”.
Tarea 3: Comprueba qué red está usando realmente un contenedor
cr0x@server:~$ docker inspect -f '{{json .NetworkSettings.Networks}}' api-1
{"app-net":{"IPAddress":"172.22.0.10","Gateway":"172.22.0.1","MacAddress":"02:42:ac:16:00:0a"}}
Qué significa: El contenedor está en app-net con una IP interna.
Decisión: Si la app espera ser accesible desde la LAN sin mapeo de puertos, bridge no es suficiente; considera macvlan o un ingreso/proxy adecuado.
Tarea 4: Confirma puertos publicados y dónde enlazan
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Ports}}'
NAMES PORTS
api-1 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp
db-1 5432/tcp
Qué significa: api-1 está expuesto en el puerto 8080 del host. db-1 no está publicado; es solo interno (bien).
Decisión: Si ves enlaces en 0.0.0.0 que no pretendías, bloquéalos (-p 127.0.0.1:... o firewall) antes de que alguien más los encuentre.
Tarea 5: Revisa las reglas NAT de Docker (iptables) y si existen
cr0x@server:~$ sudo iptables -t nat -S | sed -n '1,40p'
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-N DOCKER_OUTPUT
-N DOCKER_POSTROUTING
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER_OUTPUT
-A POSTROUTING -s 172.22.0.0/16 ! -o br-77aa88bb99cc -j MASQUERADE
Qué significa: Docker gestiona NAT para la subred bridge. Esa regla MASQUERADE es tu camino de salida.
Decisión: Si estás en sistemas solo con nftables, verifica que Docker sea compatible con tu stack de firewall. Herramientas mixtas causan confusión de “las reglas existen pero no se aplican”.
Tarea 6: Revisa la presión de conntrack (el dolor del NAT aparece aquí)
cr0x@server:~$ sudo conntrack -S
cpu=0 found=120384 invalid=42 ignore=0 insert=120410 insert_failed=0 drop=0 early_drop=0 error=0 search_restart=0
Qué significa: Los paquetes inválidos son pocos; no hay drops. Conntrack no está en llamas ahora.
Decisión: Si ves drops/insert_failed subiendo bajo carga, aumentar límites de conntrack o reducir churn/NAT de conexiones se vuelve urgente. El modo host a veces “arregla” esto evitando NAT, pero es un intercambio.
Tarea 7: Verifica ruta y MTU desde dentro del contenedor
cr0x@server:~$ docker exec api-1 ip route
default via 172.22.0.1 dev eth0
172.22.0.0/16 dev eth0 proto kernel scope link src 172.22.0.10
cr0x@server:~$ docker exec api-1 ip link show eth0
2: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:16:00:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
Qué significa: La ruta por defecto es el gateway del bridge; MTU es 1500.
Decisión: Si tu underlay es 1450 por VPN/overlay, ajusta el MTU de la red Docker o del host para que el contenedor no envíe paquetes que se pierdan.
Tarea 8: Ver la veth del lado host y la pertenencia al bridge
cr0x@server:~$ ip link show br-77aa88bb99cc
7: br-77aa88bb99cc: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:11:22:33:44 brd ff:ff:ff:ff:ff:ff
cr0x@server:~$ bridge link | grep br-77aa88bb99cc | head
21: veth6d2b1a2@if20: <BROADCAST,MULTICAST,UP,LOWER_UP> master br-77aa88bb99cc state forwarding priority 32 cost 2
Qué significa: El par veth del contenedor está adjunto al bridge y en forwarding.
Decisión: Si no ves la interfaz o no está en forwarding, probablemente tengas un problema de kernel/bridge o el contenedor está en un estado de namespace de red malo. Reiniciar Docker puede “arreglarlo”, pero primero recoge evidencia.
Tarea 9: Prueba DNS dentro de una red bridge definida por usuario
cr0x@server:~$ docker exec api-1 getent hosts db-1
172.22.0.11 db-1
Qué significa: El DNS embebido de Docker funciona; la resolución contenedor→contenedor está bien.
Decisión: Si el DNS falla aquí, no culpes primero al DNS corporativo. Comprueba que los contenedores compartan una red bridge definida por usuario; el bridge predeterminado se comporta diferente.
Tarea 10: Confirma el comportamiento de host networking comprobando la IP del contenedor (no habrá una)
cr0x@server:~$ docker run --rm --network host alpine ip addr show eth0 | sed -n '1,12p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 0c:de:ad:be:ef:01 brd ff:ff:ff:ff:ff:ff
inet 10.20.30.40/24 brd 10.20.30.255 scope global eth0
valid_lft forever preferred_lft forever
Qué significa: Estás viendo la dirección de la interfaz del host desde dentro del contenedor. Eso es lo que “network host” significa.
Decisión: Si necesitas reglas de firewall por contenedor o identidades IP distintas, el modo host es la herramienta equivocada.
Tarea 11: Crea una red macvlan con un rango IP controlado
cr0x@server:~$ docker network create -d macvlan \
--subnet=10.50.10.0/24 --gateway=10.50.10.1 \
--ip-range=10.50.10.128/25 \
-o parent=eno1 macvlan-net
8f9e0d1c2b3a4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e
Qué significa: Los contenedores pueden recibir direcciones en 10.50.10.128/25 mientras que el resto de la subred se reserva para otros usos.
Decisión: Si no puedes reservar un rango limpio y documentarlo, no uses macvlan. Los conflictos de IP son desastres en cámara lenta.
Tarea 12: Ejecuta un contenedor en macvlan y confirma que tiene una IP de LAN
cr0x@server:~$ docker run -d --name web-mv --network macvlan-net --ip 10.50.10.140 nginx:alpine
c2b1a0f9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b3a2f1e0d9c8b7a6f5e4d3c2b1
cr0x@server:~$ docker exec web-mv ip addr show eth0 | sed -n '1,10p'
2: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:0a:32:0a:8c brd ff:ff:ff:ff:ff:ff
inet 10.50.10.140/24 brd 10.50.10.255 scope global eth0
Qué significa: El contenedor es ahora un endpoint de LAN de primera clase.
Decisión: Verifica la política del switch (seguridad de puertos, límites de MAC). Si los paquetes se caen después de unos cuantos contenedores, no es “Docker inestable”. Es tu L2 aplicando reglas.
Tarea 13: Confirma la limitación clásica de macvlan: el host no puede alcanzar al contenedor (por defecto)
cr0x@server:~$ ping -c 2 10.50.10.140
PING 10.50.10.140 (10.50.10.140) 56(84) bytes of data.
--- 10.50.10.140 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1017ms
Qué significa: El tráfico host→hijo macvlan no funciona. Esto es esperado en muchas configuraciones.
Decisión: Si los servicios del host (agentes de backup, monitores locales, sidecars) deben hablar con esos contenedores, crea un shim macvlan en el host.
Tarea 14: Añade una interfaz shim macvlan en el host para alcanzar contenedores macvlan
cr0x@server:~$ sudo ip link add macvlan-shim link eno1 type macvlan mode bridge
cr0x@server:~$ sudo ip addr add 10.50.10.2/24 dev macvlan-shim
cr0x@server:~$ sudo ip link set macvlan-shim up
cr0x@server:~$ ping -c 2 10.50.10.140
PING 10.50.10.140 (10.50.10.140) 56(84) bytes of data.
64 bytes from 10.50.10.140: icmp_seq=1 ttl=64 time=0.412 ms
64 bytes from 10.50.10.140: icmp_seq=2 ttl=64 time=0.398 ms
--- 10.50.10.140 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
Qué significa: El host ahora puede alcanzar la red macvlan vía el shim.
Decisión: Hazlo persistente (systemd-networkd/NetworkManager) o desaparecerá en el reinicio y redescubrirás esta limitación en un incidente.
Tarea 15: Valida la salud de ARP/tablas de vecinos en el host
cr0x@server:~$ ip neigh show dev eno1 | head
10.50.10.1 lladdr 00:11:22:33:44:55 REACHABLE
10.50.10.140 lladdr 02:42:0a:32:0a:8c REACHABLE
10.50.10.141 lladdr 02:42:0a:32:0a:8d STALE
Qué significa: El host está aprendiendo vecinos. Si ves muchos FAILED o churn constante, macvlan podría estar estresando L2/L3.
Decisión: Si el churn de vecinos se correlaciona con pérdida de paquetes, reduce el churn de contenedores, ajusta umbrales GC o reconsidera macvlan para ese entorno.
Tarea 16: Confirma qué proceso posee un puerto (modo host y “listeners misteriosos”)
cr0x@server:~$ sudo ss -lntp | grep ':8080'
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=21457,fd=6))
Qué significa: Algo (aquí, nginx) posee el puerto 8080 en la pila de red del host.
Decisión: Si esperabas publicación de puertos de Docker pero ves un listener directo, podrías estar en modo host o ejecutar el servicio en el host por accidente. Arregla el modelo de despliegue antes de perseguir problemas fantasma de firewall.
Tarea 17: Traza el camino de paquetes rápidamente con tcpdump (bridge vs host vs macvlan)
cr0x@server:~$ sudo tcpdump -ni br-77aa88bb99cc port 5432 -c 5
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on br-77aa88bb99cc, link-type EN10MB (Ethernet), snapshot length 262144 bytes
12:01:10.112233 IP 172.22.0.10.49822 > 172.22.0.11.5432: Flags [S], seq 123456789, win 64240, options [mss 1460,sackOK,TS val 1 ecr 0,nop,wscale 7], length 0
Qué significa: Estás viendo tráfico contenedor→contenedor en el bridge. Eso confirma que el problema no es “los paquetes no salieron del contenedor”.
Decisión: Si el tráfico aparece en el bridge pero no en el uplink, centra en reglas de enrutamiento/NAT. Si aparece en el uplink pero no llega al destino, es upstream.
Guion rápido de diagnóstico
Este es el orden que ahorra tiempo. No el orden que te hace sentir como un mago de redes.
Primero: identifica el modo de red y la alcanzabilidad prevista
- Ejecuta
docker inspecten el contenedor y confirma si está en bridge/host/macvlan. - Aclara: ¿la falla es contenedor → internet, contenedor → contenedor, LAN → contenedor o host → contenedor?
Pista de cuello de botella: La mayoría de los problemas “de redes Docker” son realmente problemas de “tu modelo mental está equivocado”. Arregla el modelo, luego la configuración.
Segundo: comprueba lo obvio de L3 (dentro del contenedor y en el host)
- Dentro del contenedor:
ip addr,ip route, resolución DNS congetent hosts. - En el host: estado del bridge, presencia de veth, ruta a la subred, tabla de vecinos (macvlan).
Pista de cuello de botella: Ruta por defecto faltante o DNS malo causa 80% de las quejas “no puede alcanzar X”.
Tercero: valida política y traducción (iptables/nftables, enlaces de puertos, conntrack)
- Problemas inbound en modo bridge: comprueba puertos publicados y cadenas NAT de iptables.
- Problemas en modo host: revisa qué proceso posee el puerto y las reglas de firewall del host.
- Problemas macvlan: comprueba estado ARP/vecinos y restricciones/seguridad del switch.
- Extraños bajo alta carga: revisa estadísticas de conntrack y logs del kernel.
Pista de cuello de botella: Si el tráfico funciona brevemente y falla bajo carga, asume presión de conntrack o enforcement L2 upstream antes de culpar a Docker.
Cuarto: captura paquetes en la interfaz correcta
- Bridge:
tcpdumpen la interfaz del contenedor (dentro) y en el bridge (br-*). - Host:
tcpdumpen la interfaz del host y usa herramientas a nivel de proceso para atribuir tráfico. - Macvlan:
tcpdumpen la interfaz parent y en el shim macvlan (si se usa).
Pista de cuello de botella: Si los paquetes salen del contenedor pero nunca llegan al bridge/uplink, tienes un problema de cableado de namespace. Si alcanzan el uplink, es upstream.
Errores comunes: síntoma → causa raíz → solución
1) “El contenedor no puede alcanzar internet, pero DNS resuelve”
Síntoma: getent hosts example.com funciona; curl se queda colgado o hace timeout.
Causa raíz: Ruta por defecto faltante/incorrecta, regla MASQUERADE rota, o firewall del host bloqueando el reenvío.
Solución: Verifica ip route dentro del contenedor; en el host, confirma iptables -t nat MASQUERADE y que el reenvío IP esté habilitado. Asegura que el firewall del host permita el reenvío desde la subred del bridge.
2) “El servicio está arriba, pero nadie puede conectarse desde afuera” (bridge)
Síntoma: El contenedor escucha en 0.0.0.0:8080 internamente, pero clientes LAN no pueden conectar.
Causa raíz: Puerto no publicado, o publicado solo en localhost, o firewall del host bloquea el puerto publicado.
Solución: Revisa docker ps por 0.0.0.0:hostport->containerport. Si falta, añade -p. Luego verifica que el firewall del host permita inbound.
3) “Dos contenedores están inestables, a veces uno no arranca” (host)
Síntoma: Bucles de reinicio o errores intermitentes al enlazar; logs mencionan “address already in use.”
Causa raíz: El modo host comparte el namespace de puertos; ambos servicios quieren el mismo puerto.
Solución: Deja de usar host networking para ambos, o rediseña puertos. En modo host, trata la asignación de puertos como un recurso global por host.
4) “Los contenedores macvlan funcionan desde la LAN, pero el host no puede alcanzarlos”
Síntoma: Desde otra máquina en la subred puedes conectar; desde el host Docker falla.
Causa raíz: El comportamiento por defecto de macvlan impide la comunicación host→hijo en el mismo parent.
Solución: Añade una interfaz shim macvlan en el host con una IP en la misma subred y enruta a través de ella (o elige ipvlan L3 si corresponde en tu entorno).
5) “Todo funcionó hasta que añadimos más contenedores; luego timeouts aleatorios” (macvlan)
Síntoma: Nuevos contenedores son alcanzables a veces; ARP parece inestable; logs del switch se quejan.
Causa raíz: Seguridad de puerto del switch o límites de direcciones MAC, presión de tabla CAM, o limitación de ARP.
Solución: Coordina con el equipo de red: aumenta límites de MAC en el puerto, desactiva seguridad estricta donde proceda, o evita macvlan en ese segmento.
6) “Algunas peticiones cuelgan, especialmente respuestas grandes” (cualquier modo)
Síntoma: Pings pequeños funcionan; transferencias grandes se atascan; handshake TLS a veces falla.
Causa raíz: Desajuste de MTU y PMTUD roto.
Solución: Mide el MTU efectivo, fija MTU de la red Docker o ajusta MTU de interfaces consistentemente, y valida con pings con bit DF.
7) “Contenedor a contenedor funciona por IP, no por nombre” (bridge)
Síntoma: ping 172.22.0.11 funciona; ping db-1 falla.
Causa raíz: Los contenedores no están en la misma red bridge definida por usuario, o estás usando el bridge por defecto sin el comportamiento DNS correcto.
Solución: Coloca ambos contenedores en la misma red bridge definida por usuario y usa nombres de contenedor (o alias explícitos) allí.
Listas de verificación / plan paso a paso
Paso a paso: elegir el driver con seguridad
- Escribe la matriz de alcanzabilidad. Quién necesita hablar con quién (LAN → contenedor, contenedor → LAN, host → contenedor, contenedor → internet).
- Decide requisitos de identidad. ¿Los sistemas upstream requieren IPs por workload? Si sí, macvlan podría ser necesario; si no, prefiere bridge.
- Decide el modelo de exposición. ¿Quieres puertos publicados explícitos (bridge) o puertos compartidos del host (host)? Por defecto, elige explícito.
- Revisa el modelo de propiedad del firewall. Si el firewall del host es gestionado centralmente y los cambios de Docker no son bienvenidos, planifica la integración con cuidado (bridge) o evita patrones con mucho NAT.
- Revisa la política del switch si macvlan está en juego (límites de MAC, seguridad de puerto, inspección ARP). Haz esto antes del despliegue.
- Elige el modelo más simple que satisfaga los requisitos. Luego documéntalo para que la próxima persona no lo “optimice”.
Lista de verificación para redes bridge en producción
- Usa redes bridge definidas por usuario, no el
bridgepor defecto, para apps reales. - Elige subredes que no se solapen con rangos VPN/centro de datos.
- Publica solo puertos necesarios; enlaza a IPs específicas del host cuando sea posible.
- Decide cómo gestionarás iptables/nftables (y prueba tras actualizaciones del SO).
- Valida MTU de extremo a extremo y configúralo intencionalmente.
Lista de verificación para networking host en producción
- Reserva el modo host para workloads que lo justifiquen (ritmo de paquetes, demonios de red, agentes de host).
- Mantén un mapa de propiedad de puertos por host (o impónlo con automatización).
- Endurezca reglas de firewall del host; no confíes en los valores por defecto del contenedor.
- Verifica la atribución en observabilidad: ¿puedes vincular tráfico a un contenedor/cgroup?
Lista de verificación para macvlan en producción
- Reserva un rango IP documentado; evita mezclar con DHCP a menos que realmente controles el entorno.
- Confirma que la política del switch soporta múltiples MAC por puerto y no provocará flapping.
- Planifica el acceso host→contenedor (interfaz shim) si es necesario.
- Monitorea comportamiento ARP/tabla de vecinos y límites de tasa.
- Decide quién gestiona DNS: querrás registros directo/inverso si las herramientas corporativas los esperan.
Tres minihistorias corporativas (porque se repetirán si no)
Mini-historia 1: un incidente causado por una suposición equivocada (alcanzabilidad host con macvlan)
Una empresa mediana estaba contenerizando un servicio de reporting legado que necesitaba ser alcanzado por un conjunto de jobs upstream. Eligieron macvlan porque cada job upstream tenía una allowlist con IPs destino codificadas, y reescribir la política habría requerido capital político que no tenían.
En staging todo se veía bien. Desde otras máquinas en la subred podían alcanzar las IPs de los contenedores directamente. El cambio fue a producción un viernes por la tarde, porque claro que sí, y de inmediato sus health checks locales empezaron a fallar. El orquestador marcó instancias como unhealthy y las reinició. Ahora el servicio entró en flapping, lo que hizo que los jobs upstream fallaran más, lo que disparó más reintentos y todo se volvió más ruidoso.
La suposición equivocada: “Si la LAN puede alcanzarlo, el host puede alcanzarlo.” Con macvlan, la ruta host→hijo es una excepción conocida en muchas configuraciones. Sus health checks corrían en el namespace de red del host y no podían alcanzar la IP del contenedor macvlan. El servicio estaba bien; el host simplemente no lo veía.
La solución fue mundana: crear una interfaz shim macvlan en cada host, darle una IP en la subred macvlan y actualizar los health checks para usar ese camino. También documentaron la restricción para que nadie la “simplificara” después.
Mini-historia 2: una optimización que salió mal (host networking para evitar NAT)
Otra organización ejecutaba una canalización de ingestión de métricas de alto rendimiento. Alguien notó contadores de conntrack subiendo bajo carga y decidió que el NAT del bridge “gastaba CPU”. La solución propuesta fue simple: mover los contenedores de ingestión a --network host para que los paquetes evitaran NAT y conntrack por completo.
En un benchmark estrecho funcionó. La CPU bajó, la latencia mejoró y todos se sintieron como si hubieran descubierto un truco. El cambio se desplegó progresivamente en la flota.
Luego empezó la rareza: un subconjunto de hosts tuvo fallos intermitentes de ingestión después de despliegues. No todos los hosts, no todo el tiempo. La causa raíz fueron colisiones de puertos entre el servicio de ingestión y un sidecar de depuración que también enlazaba un puerto UDP. En modo bridge podían coexistir en namespaces diferentes. En modo host, el segundo servicio simplemente no pudo enlazar. A veces el orden de despliegue lo enmascaraba; otras explotaba.
Revirtieron el networking host para la capa de ingestión y en su lugar ajustaron la capacidad de conntrack y redujeron el churn de conexiones con batching. El verdadero cuello de botella no era “NAT es lento”. Era “creamos demasiados flujos de corta vida”. El modo host trató el síntoma e introdujo una nueva clase de fallo más difícil de razonar.
Después añadieron una regla: cualquier cambio que aumente el radio de impacto (como host networking) necesita un modelado de amenazas por escrito y un plan de rollback. Esa política fue menos emocionante que la gráfica del benchmark, pero funcionó.
Mini-historia 3: aburrido pero correcto que salvó el día (bridge definido por usuario + publicación explícita)
Un equipo de servicios financieros ejecutaba múltiples servicios orientados al cliente en hosts compartidos. Estandarizaron en redes bridge definidas por usuario por stack de aplicación y publicaron puertos explícitamente, vinculándolos a interfaces del host específicas. No fue trendy. También fue resiliente.
Una noche, una actualización de imagen de un vendor introdujo un nuevo listener de depuración dentro del contenedor. Nada malicioso; solo uno de esos defaults “oops, quedó activado”. El servicio seguía funcionando normalmente.
Porque el equipo usaba publicación de puertos explícita, el nuevo puerto interno se mantuvo interno. No apareció de repente en el host y no fue accesible desde la LAN. La monitorización no alertó, seguridad no entró en pánico y el incidente nunca fue noticia.
Su postmortem fue corto: “No tuvimos que preocuparnos porque no lo expusimos.” Ese es el tipo de victoria aburrida que solo notas al compararla con la línea temporal alternativa.
Preguntas frecuentes
1) ¿El networking bridge siempre es más lento que host?
No. El modo bridge añade overhead (veth, lookup en bridge, a menudo NAT/conntrack), pero para muchos workloads no es tu cuello de botella. Mide antes de “arreglarlo”. Si no estás saturando CPU en softirq/conntrack, bridge suele estar bien.
2) ¿Por qué debería preferir un bridge definido por usuario sobre el bridge por defecto?
Los bridges definidos por usuario ofrecen mejor comportamiento de DNS/descubrimiento de servicios, separación más clara y setups multi-red más predecibles. El bridge por defecto es compatible con legado, no pensado para producción.
3) ¿Cuándo es buena idea --network host?
Cuando el workload realmente necesita semánticas de red del host: altos ritmos de paquetes donde el overhead de NAT importa, demonios de red o agentes de host. También cuando el host es efectivamente de un solo propósito. Si no, host mode aumenta el radio de impacto y la complejidad de depuración.
4) ¿Por qué el host no puede alcanzar contenedores macvlan por defecto?
Porque macvlan aisla el tráfico entre la interfaz parent y los endpoints macvlan de una forma que impide que la pila del host hable directamente con sus hijos en esa misma interfaz. El workaround común es una interfaz shim macvlan en el host.
5) ¿Puedo ejecutar macvlan en Wi‑Fi?
A veces, pero suele ser doloroso. Muchos drivers Wi‑Fi y puntos de acceso no manejan múltiples MACs fuente por estación como quieres. Si debes intentarlo, hazlo en un laboratorio primero y prepárate para decepciones.
6) ¿Debería usar macvlan solo para evitar conflictos de puertos?
Usualmente no. Los conflictos de puertos se resuelven mejor con networking bridge más publicación de puertos, o colocando un reverse proxy/ingress delante. Macvlan cambia los conflictos de puertos por complejidad de IPAM y L2.
7) ¿Cómo evito que Docker manipule mi firewall?
Puedes restringir el comportamiento de Docker, pero no puedes fingir que no necesita reglas si quieres NAT/puertos publicados. El enfoque práctico es decidir si el firewall del host es “consciente de Docker” y probar el orden de reglas tras actualizaciones. Si tu entorno prohíbe cambios dinámicos de firewall, rediseña en torno a networking enrutado o balanceadores upstream.
8) ¿Cuál es la forma más segura de exponer servicios en modo bridge?
Publica solo los puertos requeridos, enlaza a una IP específica del host cuando proceda (por ejemplo, solo interfaz interna) y aplica reglas de firewall del host. Trata los puertos publicados como superficie de API externa.
9) ¿Cómo manejo logging y visibilidad de IP de cliente con NAT en bridge?
Puedes perder la IP origen original si el tráfico es proxy/NAT dependiendo del camino. Usa reverse proxies que pasen X-Forwarded-For o proxy protocol donde aplique, o evita NAT en ese salto (macvlan/host o un diseño enrutado) si la IP real del cliente es obligatoria.
Siguientes pasos que puedes hacer esta semana
- Inventario tus hosts: lista contenedores y anota cuáles usan host networking y por qué. Si “porque funcionó” es la razón, no es una razón.
- Migra una pila a un bridge definido por usuario si aún usas el
bridgepor defecto. Valida resolución por nombre y disciplina de publicación de puertos. - Elige un plan de subredes no solapadas para bridges Docker entre entornos (dev/stage/prod). Los solapamientos son una fuga lenta que se convierte en inundación.
- Decide tu política de macvlan: o “permitido con validación de switch y reserva de rango IP” u “no permitido”. La ambigüedad es cómo macvlan aparece en producción sin adultos en la sala.
- Escribe un runbook de una página usando el Guion rápido de diagnóstico arriba e incluye los comandos exactos que tu on-call puede ejecutar sin pensar.
Si quieres una regla que resista bajo estrés: usa bridge hasta que puedas nombrar la limitación específica del bridge que estás encontrando. Luego elige host o macvlan como una excepción deliberada, no por moda.