Ubuntu 24.04: systemd-resolved rompe el DNS de Docker — solución sin desactivar todo

¿Te fue útil?

Despliegas un contenedor, arranca bien, el healthcheck está en verde… y de pronto no puede resolver un nombre.
apt update se queda colgado. Los registros de tu aplicación dicen “Temporary failure in name resolution.”
Lo más insultante: el host resuelve nombres perfectamente.

En Ubuntu 24.04, esto a menudo choca con la misma trampa: systemd-resolved hace su trabajo,
Docker hace el suyo, y la superposición crea una situación de DNS que parece brujería. No lo es.
Es fontanería. Y la solución no requiere desactivar la mitad de tu pila de red por despecho.

Qué realmente se rompe: resolutores stub, namespaces y el DNS de Docker

Nombrémos las partes móviles, porque la falla solo parece misteriosa cuando tratas al DNS como una caja única.
En Ubuntu 24.04, el host suele usar systemd-resolved para proporcionar un “resolutor stub” local
enlazado a 127.0.0.53. El /etc/resolv.conf del host suele ser un enlace simbólico a un archivo
que apunta a ese stub.

Docker, mientras tanto, normalmente inyecta un resolutor en los contenedores copiando o generando
/etc/resolv.conf dentro del namespace de red del contenedor. Si Docker ve nameservers en el
resolv.conf del host, intentará usarlos. Aquí está la trampa: el 127.0.0.53 de un contenedor no es el host.
Es el propio contenedor. Así que cuando un contenedor intenta consultar a 127.0.0.53, básicamente se está preguntando
a sí mismo por DNS, y nadie responde.

Docker tiene un servidor DNS integrado (generalmente accesible en 127.0.0.11 dentro de contenedores)
para descubrimiento entre servicios en redes definidas por el usuario. Pero ese DNS integrado aún necesita servidores upstream
a los que reenviar las solicitudes hacia el mundo real. Si el upstream está establecido en el stub del host (127.0.0.53), el reenvío falla.

Hay otras variantes:

  • Docker recoge nameserver 127.0.0.53 del host y lo escribe en los contenedores. Falla inmediatamente.
  • Docker usa su DNS integrado (127.0.0.11) pero el upstream está roto, por lo que los nombres internos funcionan y los externos fallan.
  • El DNS dividido (dominios corporativos enrutados a resolutores específicos) funciona en el host vía systemd-resolved,
    pero los contenedores evitan esa lógica dividida y consultan DNS público para zonas internas, obteniendo NXDOMAIN o timeouts.

El objetivo aquí no es “ganar” desactivando systemd-resolved. Aporta valor legítimo:
DNS por enlace, estado DNSSEC, caché y buena integración con NetworkManager y netplan. El objetivo es lograr que
Docker use un resolv.conf que contenga servidores upstream alcanzables, y hacerlo de forma predecible.

Una cita para enmarcar la mentalidad (idea parafraseada): John Allspaw ha impulsado la idea de que la fiabilidad viene de entender los sistemas, no de culpar a las personas.
Las fallas de DNS casi siempre son interacciones entre sistemas, no incompetencia del operador.

Broma #1: El DNS es la única parte de la pila donde “funcionó ayer” se considera un formato de configuración.

Hechos interesantes y contexto histórico (para que deje de parecer aleatorio)

  1. El stub de systemd-resolved en 127.0.0.53 existe para evitar colisiones con resolutores locales como dnsmasq,
    y para mantener un endpoint de loopback consistente incluso cuando los upstream cambian.
  2. El DNS integrado de Docker (127.0.0.11) se introdujo para soportar el descubrimiento entre contenedores en
    redes definidas por el usuario, no como un “cliente DNS corporativo” de propósito general.
  3. La librería resolutora de glibc lee /etc/resolv.conf y tiene reglas como “3 nameservers máximo”
    que pueden descartar entradas silenciosamente, lo cual es divertido cuando clientes VPN añaden cinco resolutores.
  4. Ubuntu se ha integrado más con systemd en las últimas versiones; que /etc/resolv.conf
    sea un enlace simbólico ahora es normal, no exótico.
  5. El DNS dividido solía ser raro fuera de empresas. Ahora incluso usuarios domésticos lo encuentran con VPNs en malla, herramientas de desarrollo,
    y DNS “mágico” para dominios internos.
  6. Los namespaces de red significan que el loopback es por namespace. 127.0.0.1 dentro de un contenedor no es el host.
    Si recuerdas solo una cosa, recuerda esa.
  7. resolv.conf en contenedores no es sagrado. Se genera al iniciar el contenedor y puede cambiar según la configuración de Docker, el driver de red y el runtime.
  8. systemd-resolved puede exponer un resolv.conf “real” que lista servidores upstream vía
    /run/systemd/resolve/resolv.conf, que suele ser la fuente más limpia para que Docker la consuma.

Guía rápida de diagnóstico

Cuando la producción está haciendo paging y necesitas señal rápido, no empieces editando archivos de configuración.
Empieza por averiguar dónde muere la resolución: dentro del contenedor, en el DNS integrado de Docker, o en la capa resolutora del host.

Primero: confirma qué falla y dónde

  • Dentro del contenedor: ¿puedes resolver algo? ¿Nombres públicos? ¿Nombres internos?
  • Dentro del contenedor: ¿qué contiene realmente /etc/resolv.conf?
  • Host: ¿está systemd-resolved sano y qué servidores upstream conoce?

Segundo: clasifica la falla

  • nameserver 127.0.0.53 dentro del contenedor → casi con seguridad propagación incorrecta del resolv.conf.
  • nameserver 127.0.0.11 dentro del contenedor, pero timeouts → el upstream del DNS integrado de Docker es incorrecto/inalcanzable.
  • Lo público funciona, lo corporativo falla → discrepancia de DNS dividido entre el host y los contenedores.
  • Sólo ciertas redes fallan → ruta VPN, firewall o MTU/fragmentación afectando el tráfico DNS.

Tercero: arregla en la capa más estrecha posible

  • Prefiere apuntar Docker a un resolv.conf real o a servidores DNS explícitos.
  • Usa ajustes de DNS por red o por Compose cuando corresponda.
  • Evita desactivar systemd-resolved a menos que lo reemplaces por un resolutor mejor definido y aceptes el radio de impacto.

Tareas prácticas: comandos, salida esperada y qué decisión tomar

Estas son las comprobaciones que realmente ejecuto en hosts Ubuntu cuando los contenedores empiezan a fallar en DNS.
Cada tarea incluye (1) un comando, (2) qué significa la salida, y (3) la decisión que tomas a partir de ello.
Ejecútalas en orden hasta tener un diagnóstico confiable.

Tarea 1: Confirma el síntoma de DNS de Docker desde dentro de un contenedor mínimo

cr0x@server:~$ docker run --rm alpine:3.20 sh -lc 'cat /etc/resolv.conf; nslookup -type=a example.com 2>&1 | sed -n "1,12p"'
nameserver 127.0.0.53
options edns0 trust-ad
nslookup: can't resolve '(null)': Name does not resolve

Significado: el contenedor intenta hablar con 127.0.0.53 (sí mismo), no con el resolutor del host.
La consulta falla inmediatamente.
Decisión: deja de culpar al DNS upstream. Arregla cómo Docker popula el resolv.conf del contenedor (o el DNS del daemon de Docker).

Tarea 2: Comprueba si el resolv.conf del host es el stub de systemd

cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Oct 10 09:12 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

Significado: el host apunta al archivo del stub del resolutor.
Decisión: si Docker está heredando esto, pondrá en los contenedores un nameserver sin salida. Quieres que Docker use el resolv.conf “real”.

Tarea 3: Inspecciona el resolv.conf “real” que lista servidores upstream

cr0x@server:~$ sed -n '1,20p' /run/systemd/resolve/resolv.conf
# This is /run/systemd/resolve/resolv.conf managed by man:systemd-resolved(8).
nameserver 10.20.0.10
nameserver 10.20.0.11
search corp.example

Significado: estos son resolutores upstream alcanzables desde la perspectiva de la red del host.
Decisión: la solución recomendada suele ser: hacer que Docker lea este archivo en lugar del archivo stub.

Tarea 4: Verifica que systemd-resolved esté en ejecución y no degradado

cr0x@server:~$ systemctl status systemd-resolved --no-pager -l
● systemd-resolved.service - Network Name Resolution
     Loaded: loaded (/usr/lib/systemd/system/systemd-resolved.service; enabled; preset: enabled)
     Active: active (running) since Thu 2025-12-28 10:20:18 UTC; 3h 12min ago
       Docs: man:systemd-resolved.service(8)
   Main PID: 812 (systemd-resolve)
     Status: "Processing requests..."
      Tasks: 1 (limit: 38291)
     Memory: 6.1M
        CPU: 1.228s

Significado: resolved está sano.
Decisión: no “arregles” esto desactivándolo. Tu problema es la integración con Docker, no la salud del resolutor.

Tarea 5: Ver qué servidores DNS cree que debe usar resolved por enlace

cr0x@server:~$ resolvectl status
Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
 resolv.conf mode: stub

Link 2 (ens160)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 10.20.0.10
       DNS Servers: 10.20.0.10 10.20.0.11
        DNS Domain: corp.example

Significado: el host usa el DNS de ens160, y tiene un dominio de búsqueda.
Decisión: si los contenedores necesitan resolver corp.example, debes hacer que Docker use estos servidores upstream o perderás el comportamiento de DNS dividido.

Tarea 6: Comprueba la vista de Docker sobre el resolv.conf del host que usará

cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 127.0.0.53

Significado: el daemon de Docker está configurado (explícita o implícitamente) para usar el resolutor stub.
Decisión: sobreescribe el DNS de Docker para usar upstream reales o apunta Docker al resolv.conf no-stub.

Tarea 7: Confirma qué resolv.conf recibió realmente un contenedor en ejecución

cr0x@server:~$ cid=$(docker run -d alpine:3.20 sleep 300); docker exec "$cid" cat /etc/resolv.conf; docker rm -f "$cid" >/dev/null
nameserver 127.0.0.53
options edns0 trust-ad

Significado: esto no es el DNS integrado de Docker; está heredando directamente el stub.
Decisión: arregla la entrada que usa Docker (resolv.conf del host o la configuración DNS del daemon).

Tarea 8: Comprueba si el DNS integrado de Docker está presente (escenario 127.0.0.11)

cr0x@server:~$ docker network create testnet >/dev/null
cr0x@server:~$ cid=$(docker run -d --network testnet alpine:3.20 sleep 300)
cr0x@server:~$ docker exec "$cid" cat /etc/resolv.conf
nameserver 127.0.0.11
options ndots:0
cr0x@server:~$ docker rm -f "$cid" >/dev/null; docker network rm testnet >/dev/null

Significado: en redes definidas por el usuario, Docker usa su DNS integrado.
Decisión: si las consultas aún fallan aquí, el upstream usado por el DNS integrado de Docker es el problema.

Tarea 9: Inspecciona reglas de iptables/nft que permiten el reenvío DNS de contenedores

cr0x@server:~$ sudo nft list ruleset | sed -n '1,80p'
table inet filter {
  chain input {
    type filter hook input priority filter; policy accept;
  }
  chain forward {
    type filter hook forward priority filter; policy accept;
  }
}
table ip nat {
  chain POSTROUTING {
    type nat hook postrouting priority srcnat; policy accept;
    oifname "ens160" ip saddr 172.17.0.0/16 masquerade
  }
}

Significado: existe NAT masquerade, y la política forward es accept (en este fragmento).
Decisión: si ves políticas forward restrictivas o falta masquerade, las consultas DNS pueden no salir del host. Arregla reglas de firewall antes de tocar la configuración DNS.

Tarea 10: Observa los logs de systemd-resolved durante un intento de DNS desde contenedor

cr0x@server:~$ sudo journalctl -u systemd-resolved -n 30 --no-pager
Dec 28 13:21:04 server systemd-resolved[812]: Using degraded feature set UDP instead of UDP+EDNS0 for DNS server 10.20.0.10.
Dec 28 13:21:10 server systemd-resolved[812]: DNS server 10.20.0.11 does not support DNSSEC, disabling DNSSEC validation for this server.

Significado: resolved está negociando capacidades; estos mensajes no son fatales por sí mismos.
Decisión: si hay timeouts, avalanchas de SERVFAIL, o conmutaciones frecuentes de servidor, también tienes problemas con el upstream DNS. No te detengas en el problema del stub de Docker.

Tarea 11: Valida que el host puede resolver lo que los contenedores no pueden (comprobación de DNS dividido)

cr0x@server:~$ resolvectl query registry.corp.example
registry.corp.example: 10.30.40.50                      -- link: ens160

-- Information acquired via protocol DNS in 12.7ms.
-- Data is authenticated: no

Significado: el host resuelve un nombre corporativo a través del DNS configurado en su enlace.
Decisión: si los contenedores no pueden resolver esto, necesitas pasar esos resolutores corporativos a Docker, no DNS públicos.

Tarea 12: Valida la resolución en contenedor contra servidores DNS explícitos (prueba rápida)

cr0x@server:~$ docker run --rm --dns 10.20.0.10 --dns 10.20.0.11 alpine:3.20 sh -lc 'nslookup example.com | sed -n "1,8p"'
Server:		10.20.0.10
Address:	10.20.0.10:53

Non-authoritative answer:
Name:	example.com
Address: 93.184.216.34

Significado: con upstreams explícitos, el contenedor resuelve bien.
Decisión: tu solución permanente es configurar el DNS del daemon de Docker (o DNS por Compose/red) para usar servidores alcanzables o el resolv.conf real.

Tarea 13: Comprueba la presencia y configuración efectiva del archivo de configuración del daemon de Docker

cr0x@server:~$ sudo test -f /etc/docker/daemon.json && sudo cat /etc/docker/daemon.json || echo "no /etc/docker/daemon.json"
no /etc/docker/daemon.json

Significado: no existe configuración explícita del daemon.
Decisión: puedes añadir de forma segura un daemon.json mínimo para controlar el DNS en lugar de jugar con enlaces simbólicos.

Tarea 14: Confirma qué resolv.conf usa Docker cuando cambia el archivo del host

cr0x@server:~$ sudo readlink -f /etc/resolv.conf
/run/systemd/resolve/stub-resolv.conf

Significado: Docker comúnmente tomará esto en el arranque del daemon, no necesariamente dinámicamente por contenedor.
Decisión: si cambias el enlace simbólico del resolv.conf del host, reinicia Docker para asegurarte de que lo re-lea (planifica ventana de mantenimiento si es necesario).

Broma #2: Nada dice “alta disponibilidad” como desplegar un arreglo de DNS que requiere reiniciar un daemon a las 14:00 un martes.

Patrones de solución que funcionan (sin desactivar systemd-resolved)

Hay tres enfoques sensatos. Elige uno según cuán “empresarial” sea tu DNS y cuánto valores mantener la red del host estándar.
Te diré qué haría en producción para cada caso.

Patrón A (recomendado): decirle a Docker que use servidores DNS upstream reales

Esto es lo más limpio en el sentido de “hacer que las fallas sean aburridas”. Evitas que Docker herede el resolutor stub y le das
los resolutores a los que debería reenviar.

Crea o edita /etc/docker/daemon.json:

cr0x@server:~$ sudo install -d -m 0755 /etc/docker
cr0x@server:~$ sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "dns": ["10.20.0.10", "10.20.0.11"],
  "dns-search": ["corp.example"]
}
EOF
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ docker info 2>/dev/null | sed -n '/DNS/,+3p'
 DNS: 10.20.0.10
 DNS: 10.20.0.11

Qué hace esto: el DNS integrado de Docker y/o el resolv.conf del contenedor ahora apuntan a resolutores alcanzables desde los contenedores (vía NAT del host).
Por qué es bueno: determinista, sin juegos de enlaces simbólicos, resistente a actualizaciones de Ubuntu.
Inconveniente: si el DNS upstream cambia (DHCP, portátiles que se mueven), debes actualizar esta configuración o automatizarla.

Patrón B: apuntar /etc/resolv.conf del host al archivo no-stub de systemd-resolved

Esto es común y funciona bien en servidores con red estable. El truco: no estás desactivando resolved; solo haces
que /etc/resolv.conf liste servidores upstream reales en lugar de 127.0.0.53.
Docker entonces hereda resolutores utilizables.

cr0x@server:~$ sudo mv /etc/resolv.conf /etc/resolv.conf.bak
cr0x@server:~$ sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf
cr0x@server:~$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 32 Dec 28 14:01 /etc/resolv.conf -> /run/systemd/resolve/resolv.conf
cr0x@server:~$ sudo systemctl restart docker

Qué hace esto: el resolvedor glibc del host ahora consulta servidores upstream directamente.
Por qué es bueno: Docker hereda buenos nameservers sin configuración adicional del daemon.
Inconveniente: algunas configuraciones dependen del modo stub para comportamientos específicos; además, portátiles/VPN pueden rotar mucho el DNS.
Si este host se mueve entre redes, preferiría configurar Docker explícitamente.

Patrón C: anulaciones de DNS por Compose / por contenedor (quirúrgico, a veces necesario)

Esto es lo que haces cuando una pila necesita DNS corporativo y otra debe usar resolutores públicos, o cuando un contenedor de un proveedor
hace algo raro con DNS. También es lo que haces cuando no controlas la configuración del daemon del host (hola, runners compartidos).

Ejemplo con Docker Compose:

cr0x@server:~$ cat compose.yaml
services:
  app:
    image: alpine:3.20
    command: ["sh", "-lc", "cat /etc/resolv.conf; nslookup example.com | sed -n '1,8p'; sleep 3600"]
    dns:
      - 10.20.0.10
      - 10.20.0.11
    dns_search:
      - corp.example

Qué hace esto: anula el resolv.conf dentro del contenedor.
Por qué es bueno: radio de impacto limitado; amigable para control de cambios.
Inconveniente: proliferación de configuración. Si tienes 40 proyectos Compose, olvidarás uno.

Qué evitar: “simplemente desactivar systemd-resolved” como reflejo

Puedes perfectamente eliminar systemd-resolved y usar un resolv.conf estático u otro resolutor local.
A veces es la opción correcta, especialmente si ya ejecutas dnsmasq/unbound y quieres una pila más simple.
Pero como respuesta por defecto, es desproporcionado.

Desactivar resolved a menudo rompe:

  • El comportamiento de DNS dividido de VPN integrado con NetworkManager
  • La configuración DNS por interfaz en hosts multi-homed
  • Sistemas que esperan el stub de resolved (varias herramientas de escritorio y de desarrollo)

Si quieres fiabilidad, no elimines componentes hasta que puedas explicar exactamente qué estaban haciendo por ti.

Un detalle sutil en producción: comportamiento al reiniciar y ciclo de vida de contenedores

Docker normalmente escribe el /etc/resolv.conf de un contenedor al iniciar el contenedor. Si arreglas el DNS y no reinicias contenedores,
algunos mantendrán la configuración rota antigua. Así que el plan de despliegue importa:

  • Arregla el DNS del daemon de Docker.
  • Reinicia Docker si es necesario.
  • Reimplementa / reinicia contenedores para refrescar su resolv.conf.

Tres mini-historias del mundo corporativo (cómo falla esto en organizaciones reales)

Mini-historia 1: el incidente causado por una suposición errónea

Un equipo con el que trabajé ejecutaba agentes de build en hosts Ubuntu. Nada sofisticado: compilaciones con Docker, algunas pruebas de integración,
luego subir artefactos a un registro interno. Llegó una actualización rutinaria del SO: Ubuntu 24.04, actualizaciones de kernel,
un reinicio, y todo parecía bien. El host resolvía cualquier cosa. ¿Los agentes CI? La mitad empezó a fallar
con “could not resolve host” a mitad del pipeline.

La suposición errónea fue dolorosamente simple: “Si el host resuelve DNS, los contenedores también”.
Esa creencia se sostuvo años en configuraciones antiguas porque el /etc/resolv.conf del host listaba resolutores upstream reales.
La actualización lo cambió al stub de systemd, y Docker copió diligentemente la dirección del stub en los contenedores.
Los contenedores entonces se preguntaban a sí mismos por respuestas DNS. No eran tan sabios como parecían.

La respuesta inicial fue teatro corporativo clásico: abrir tickets con el equipo de red, preguntar si el DNS estaba caído,
reintentar builds, culpar a “infra flaky”. Mientras tanto, la pista estaba a la vista: nameserver 127.0.0.53
dentro del contenedor. La solución fue establecer el DNS del daemon de Docker a los resolutores corporativos y reiniciar el servicio
Docker durante una ventana de mantenimiento.

La lección duradera no fue “systemd-resolved es malo”. Fue “los namespaces cambian lo que significa localhost”.
En cada revisión de incidentes desde entonces, el equipo añadió una línea: siempre verificar el /etc/resolv.conf
del contenedor antes de diagnosticar el DNS upstream.

Mini-historia 2: la optimización que salió mal

Otra organización decidió “reducir latencia” fijando resolutores públicos en Docker en todas las máquinas de desarrollo.
La razón parecía razonable: menos piezas móviles, no depender del DNS corporativo, y resolución más rápida que por la VPN.
Implementaron un estándar en /etc/docker/daemon.json con dos resolutores públicos.

Funcionó durante una semana. Luego las herramientas internas empezaron a fallar en contenedores: mirrors de paquetes, hosts Git internos,
descubrimiento de servicios bajo un dominio privado. En el host seguía funcionando porque systemd-resolved
aplicaba DNS dividido: dominios corporativos iban a resolutores corporativos, el resto a públicos.
Los contenedores omitieron esa lógica y consultaron DNS público para zonas privadas, devolviendo NXDOMAIN rápida y contundentemente.

La “optimización” no solo falló; falló rápido, de la forma más engañosa. Los desarrolladores veían “el host funciona, el contenedor no”,
asumieron que la red de Docker estaba rota, y empezaron a añadir trucos: entradas --add-host extra, ajustes DNS por proyecto,
y caches que enmascaraban el problema hasta el próximo cambio de red.

El rollback fue dejar de forzar DNS públicos globalmente, y en su lugar configurar Docker para usar los mismos resolutores upstream que el host,
o usar DNS por proyecto solo cuando existiera una excepción legítima. La victoria más profunda fue la política: el DNS es parte de la paridad del entorno.
Si tu entorno de desarrollo en contenedores no sigue las mismas reglas de enrutamiento DNS que producción, depurarás fantasmas.

Mini-historia 3: la práctica aburrida pero correcta que salvó el día

Un equipo SRE tenía una práctica poco glamorosa: cada host tenía un pequeño script de “sanidad de red” ejecutado por cron y también disponible como fragmento de runbook.
Comprobaba tres cosas: resolución DNS del host vía resolvectl, resolución DNS de contenedores mediante un contenedor mínimo conocido,
y si el /etc/resolv.conf del host era stub o upstream.

Cuando Ubuntu 24.04 se desplegó, las alertas empezaron a sonar antes de que los clientes notaran nada: “mismatch de DNS en contenedores detectado.”
Nada estaba caído aún porque la mayoría de servicios usaban contenedores ya en ejecución con resultados en caché, pero el equipo sabía
que el siguiente deploy fallaría.

Usaron la salida del script para clasificar el problema como “stub propagado a contenedores” y aplicaron una solución estándar:
fijar el DNS del daemon de Docker a resolutores upstream tomados de /run/systemd/resolve/resolv.conf, luego reiniciar Docker
en una ventana controlada, y reiniciar primero los servicios sin estado.

La práctica no era glamorosa. No involucraba service meshes ni IA. Era validación básica en los límites
entre host y contenedor. Y convirtió lo que pudo haber sido un incidente multi-equipo en un cambio planificado.

Errores comunes: síntomas → causa raíz → solución

1) Los contenedores muestran “Temporary failure in name resolution” inmediatamente

Síntoma: cualquier búsqueda DNS falla instantáneamente dentro de contenedores; el host funciona.

Causa raíz: el /etc/resolv.conf del contenedor tiene nameserver 127.0.0.53.

Solución: configura el DNS del daemon de Docker (/etc/docker/daemon.json) o apunta el resolv.conf del host a
/run/systemd/resolve/resolv.conf, luego reinicia Docker y los contenedores afectados.

2) Nombres de servicios internos resuelven, nombres externos timeout

Síntoma: ping other-container funciona en una red definida por el usuario, pero apt update falla.

Causa raíz: el DNS integrado de Docker (127.0.0.11) funciona para descubrimiento interno, pero el reenvío upstream falla
debido a herencia del resolutor stub o a resolutores upstream inalcanzables.

Solución: configura el DNS del daemon de Docker a resolutores upstream reales; confirma que NAT/firewall permita salida UDP/TCP 53.

3) Funciona fuera de la VPN, falla en la VPN (o viceversa)

Síntoma: los dominios corporativos fallan solo cuando la VPN está conectada.

Causa raíz: el host usa DNS dividido vía resolved; los contenedores están apuntando a resolutores públicos o antiguos.

Solución: alimenta a Docker con los mismos resolutores corporativos (y dominios de búsqueda) que usa el host, o añade DNS por Compose para pilas que lo requieran.

4) Solo algunos contenedores fallan después de un cambio de DNS

Síntoma: contenedores antiguos siguen funcionando; los nuevos despliegues fallan.

Causa raíz: los contenedores mantienen el resolv.conf con el que fueron creados; los nuevos reciben la configuración rota nueva.

Solución: después de arreglar el DNS de Docker, reinicia los contenedores (prioriza los sin estado), o recrea stacks compose.

5) El DNS funciona para consultas pequeñas pero falla para respuestas grandes

Síntoma: algunas búsquedas tienen éxito; otras cuelgan o devuelven SERVFAIL; a menudo peor en VPN.

Causa raíz: problemas de MTU/fragmentación que causan pérdida de fragmentos UDP, o problemas con tamaños EDNS0 en la ruta.

Solución: reduce la MTU en el bridge de docker o en la interfaz VPN, o asegúrate de que el fallback a TCP funcione; revisa logs de resolved por “degraded feature set.”

6) “Arreglaste” poniendo 8.8.8.8 y ahora las cosas internas están rotas

Síntoma: dominios públicos resuelven; dominios internos no.

Causa raíz: evitaste el DNS corporativo y las zonas con visión dividida.

Solución: usa resolutores corporativos como upstream para Docker, no públicos, a menos que estés seguro de que tus cargas nunca necesitarán DNS interno.

7) El DNS del host se rompe después de cambiar el enlace simbólico de /etc/resolv.conf

Síntoma: el host deja de resolver o se comporta de forma inconsistente tras cambiar enlaces simbólicos.

Causa raíz: herramientas que esperan el modo stub; o sobrescribiste resolv.conf de forma que lucha con NetworkManager/netplan.

Solución: prefiere la configuración del daemon de Docker (patrón A). Si haces cambios de enlaces simbólicos, guarda una copia y verifica con resolvectl.

Listas de verificación / plan paso a paso

Plan A (recomendado): servidores estables, controlas el daemon de Docker

  1. Confirma la falla: ejecuta un contenedor de una sola vez y comprueba /etc/resolv.conf.
    Si ves 127.0.0.53, procede.
  2. Identifica los resolutores upstream desde /run/systemd/resolve/resolv.conf o resolvectl status.
  3. Configura el DNS del daemon de Docker en /etc/docker/daemon.json con esos servidores upstream.
  4. Reinicia Docker en una ventana controlada.
  5. Reinicia contenedores (restart/recreate) para refrescar el resolv.conf de los contenedores.
  6. Valida tanto dominios públicos como internos (si procede).
  7. Documenta la decisión en un runbook: “Fijamos DNS de Docker a resolutores upstream; no usar 127.0.0.53.”

Plan B: debes preservar exactamente el comportamiento de DNS dividido

  1. Usa los resolutores corporativos que resolved reporta por enlace, e incluye dominios de búsqueda si hace falta.
  2. Para pilas que deben resolver múltiples zonas de forma distinta, prefiere anulaciones DNS por Compose en lugar de una configuración global.
  3. Valida con resolvectl query en el host y nslookup dentro de contenedores para los mismos nombres.

Plan C: mitigación a corto plazo mientras esperas control de cambios

  1. Ejecuta contenedores críticos con --dns explícito para restaurar la funcionalidad.
  2. Documenta qué servicios tocaste y por qué, porque esto si no se convertirá en “configuración misteriosa” en tres meses.
  3. Programa la corrección a nivel de daemon para evitar configuraciones permanentes tipo “snowflake”.

Preguntas frecuentes

1) ¿Por qué 127.0.0.53 funciona en el host pero no en contenedores?

Porque el loopback es por namespace de red. El 127.0.0.53 del contenedor no es el resolutor del host; está dentro del contenedor.
A menos que ejecutes resolved dentro de ese contenedor (no lo hagas), nadie está escuchando allí.

2) ¿No se supone que Docker debe usar 127.0.0.11 para DNS?

A menudo sí, especialmente en redes definidas por el usuario. Pero el DNS integrado de Docker aún reenvía a resolutores upstream.
Si Docker aprendió upstream como 127.0.0.53 desde el host, el reenvío falla igualmente.

3) ¿Puedo simplemente cambiar /etc/resolv.conf al archivo no-stub?

Puedes, y a menudo funciona bien en servidores. En portátiles o entornos con mucha VPN puede comportarse distinto que el modo stub.
Si quieres la ruta menos sorprendente, configura explícitamente el DNS del daemon de Docker.

4) ¿Necesito reiniciar Docker después de cambiar ajustes DNS?

Sí para cambios a nivel de daemon. También reinicia o recrea contenedores para que su /etc/resolv.conf se regenere con los nuevos ajustes.
De lo contrario arreglarás el host y mantendrás contenedores rotos en ejecución.

5) ¿Y si mis servidores DNS vienen por DHCP y cambian frecuentemente?

Entonces fijar DNS en /etc/docker/daemon.json es frágil salvo que lo automatices.
Para hosts que se mueven, considera una pequeña automatización que actualice el DNS de Docker desde /run/systemd/resolve/resolv.conf y reinicie Docker en ventanas seguras.

6) ¿Por qué no poner DNS público en Docker y seguir adelante?

Porque el DNS dividido existe. Las zonas internas no existen públicamente, y algunas organizaciones sirven respuestas distintas
interna vs externamente. El DNS público puede “funcionar” hasta que toques algo corporativo, entonces falla de forma confusa.

7) ¿Esto afecta a Kubernetes/containerd también?

Sí, en el fondo. Las mecánicas exactas difieren (CNI, ajustes resolvConf del kubelet), pero el problema subyacente permanece:
los contenedores necesitan resolutores alcanzables desde su namespace. Los resolutores stub en loopback son una trampa recurrente.

8) Veo timeouts, no fallos inmediatos. ¿Sigue siendo problema del stub?

A veces. Los fallos inmediatos “can’t resolve” suelen apuntar a 127.0.0.53 dentro del contenedor.
Los timeouts también pueden indicar problemas de firewall/NAT, enrutamiento VPN, MTU o salud del resolutor upstream. Usa la guía rápida de diagnóstico para clasificarlo.

9) ¿Puedo ejecutar una caché DNS en el host y apuntar Docker a ella?

Sí, pero hazlo deliberadamente. Si ejecutas unbound/dnsmasq enlazado a una dirección no loopback alcanzable desde las redes de contenedores,
los contenedores pueden usarlo. Enlazar solo a 127.0.0.1 recrea el problema del namespace en otra forma.

Conclusión: siguientes pasos que realmente puedes desplegar

La falla recurrente Ubuntu 24.04 + DNS de Docker no es una historia de “Docker está roto”. Es una discrepancia predecible:
systemd-resolved anuncia un stub en loopback, y Docker (o tus contenedores) no pueden alcanzar ese loopback en un namespace distinto.
Una vez que lo ves, no puedes dejar de verlo.

Pasos prácticos siguientes:

  1. Ejecuta la comprobación con un contenedor de una vez y mira /etc/resolv.conf. No adivines.
  2. Si ves 127.0.0.53, elige un patrón de solución: DNS del daemon (preferido) o enlace resolv.conf al archivo no-stub.
  3. Reinicia Docker en una ventana controlada, luego reinicia/recrea contenedores para que adopten la nueva configuración de resolutor.
  4. Valida dominios públicos e internos (si tienes DNS dividido, asume que sí).
  5. Escribe la decisión en tu runbook para que la próxima actualización no vuelva a enseñar la misma lección de la forma difícil.

La mejor solución de DNS es la que convierte el DNS nuevamente en infraestructura aburrida. Guarda tu adrenalina para cosas que la merecen.

← Anterior
Postfix «Relay access denied»: corregir el reenvío sin crear un relay abierto
Siguiente →
Autovacuum de PostgreSQL en Ubuntu 24.04: cómo ajustarlo de forma segura ante la “lentitud misteriosa”

Deja un comentario