Déboguer les problèmes de résolution DNS dans des conteneurs Docker
Les problèmes DNS dans Docker sont parmi les plus frustrants : tout semble “réseau OK” (ping par IP, routes, NAT), mais dès qu’un conteneur doit résoudre un nom (api.exemple.com, deb.debian.org, registry-1.docker.io), tout casse. Ce tutoriel propose une méthode systématique pour diagnostiquer et corriger les pannes de résolution DNS dans des conteneurs Docker, avec des commandes réelles, des explications approfondies et des cas concrets.
1) Comprendre le chemin DNS “réel” d’un conteneur Docker
Avant de déboguer, il faut comprendre qui fait quoi :
- Votre application (dans le conteneur) interroge un résolveur via
/etc/resolv.conf. - Dans Docker, ce
/etc/resolv.confest souvent généré (ou monté) par Docker. - Sur Linux, Docker peut injecter un serveur DNS spécial :
127.0.0.11(DNS embarqué Docker) pour les réseaux bridge/user-defined. - Ce DNS embarqué fait deux choses :
- Résolution des noms de services Docker (sur réseaux user-defined, ex:
db,redis). - Relais vers les serveurs DNS amont (ceux de l’hôte, ou ceux configurés dans Docker).
- Résolution des noms de services Docker (sur réseaux user-defined, ex:
Sur l’hôte, la résolution peut être gérée par :
systemd-resolved(souvent127.0.0.53)NetworkManager- un DNS d’entreprise (split-horizon, DNS internes)
- un VPN qui pousse des DNS spécifiques
- des règles iptables / nftables qui filtrent UDP/TCP 53
Le piège classique : l’hôte résout, mais le conteneur non, car le conteneur n’utilise pas exactement le même chemin (DNS embarqué, NAT, règles firewall, MTU, etc.).
2) Symptômes typiques et premières vérifications
Symptômes fréquents
curl: (6) Could not resolve host: ...Temporary failure in name resolutionnslookupoudigbloquent (timeout)- Résolution aléatoire (flaky), surtout sous charge
- Résolution OK pour certains domaines, pas pour d’autres (DNS interne/VPN)
Vérifier rapidement depuis un conteneur
Lancez un conteneur de diagnostic (pratique, léger) :
docker run --rm -it --name netshoot nicolaka/netshoot bash
Dans ce shell :
cat /etc/resolv.conf
ip a
ip route
Puis testez :
getent hosts google.com
nslookup google.com
dig google.com +time=2 +tries=1
getentteste la résolution via la libc (ce que fait souvent votre app).nslookup/digtestent plus directement le DNS.
Si getent échoue mais dig marche (ou l’inverse), c’est un indice sur le type de problème (nsswitch, libc, DNS TCP/UDP, etc.).
3) Inspecter /etc/resolv.conf et comprendre ce qu’il signifie
Dans un conteneur Docker, /etc/resolv.conf ressemble souvent à :
nameserver 127.0.0.11
options ndots:0
Que signifie 127.0.0.11 ?
C’est le DNS interne Docker (embedded DNS server). Il écoute dans le namespace réseau du conteneur. Il n’est pas “sur Internet” : il relaie vers des DNS amont.
Pour voir les DNS amont que Docker pense utiliser, inspectez la configuration du daemon ou du conteneur :
docker inspect <container_id> --format '{{json .HostConfig.Dns}}'
docker inspect <container_id> --format '{{json .NetworkSettings.Networks}}' | head
Sur l’hôte, vérifiez aussi :
cat /etc/docker/daemon.json 2>/dev/null || true
docker info | sed -n '/DNS/,+3p'
Cas systemd-resolved (127.0.0.53) : piège courant
Sur l’hôte, /etc/resolv.conf peut pointer vers :
nameserver 127.0.0.53
Or, 127.0.0.53 dans un conteneur n’est pas l’hôte : c’est le loopback du conteneur. Docker évite souvent de copier tel quel ce DNS, mais selon les distributions/configurations, on peut se retrouver avec des DNS inadaptés.
Vérifiez le vrai fichier resolv de systemd-resolved :
ls -l /etc/resolv.conf
cat /run/systemd/resolve/resolv.conf
resolvectl status
Si l’hôte utilise systemd-resolved, il est souvent préférable de configurer Docker pour utiliser des DNS “réels” (IP du LAN, DNS publics, DNS du VPN) plutôt que 127.0.0.53.
4) Vérifier si le problème est DNS… ou réseau vers le DNS
Un conteneur peut échouer à résoudre parce qu’il n’arrive pas à joindre le serveur DNS (UDP/TCP 53).
Tester la connectivité vers un DNS connu
Depuis le conteneur (netshoot) :
ping -c 1 1.1.1.1
Puis test DNS direct (sans passer par 127.0.0.11) :
dig @1.1.1.1 google.com +time=2 +tries=1
dig @8.8.8.8 google.com +time=2 +tries=1
Si ping 1.1.1.1 marche mais dig @1.1.1.1 timeoute, suspectez :
- filtrage UDP 53 (firewall local, réseau d’entreprise)
- VPN qui bloque/force DNS
- règles iptables/nftables sur l’hôte
- MTU/fragmentation (moins fréquent mais réel)
Vérifier si UDP 53 est filtré, tester TCP 53
Certains environnements bloquent UDP 53, mais autorisent TCP 53 (ou l’inverse). Testez :
dig @1.1.1.1 google.com +tcp +time=2 +tries=1
Si TCP marche mais pas UDP, vous avez un filtrage UDP. Il faudra adapter le réseau/pare-feu (ou utiliser un résolveur interne autorisé).
5) Diagnostiquer le DNS embarqué Docker (127.0.0.11)
Le DNS Docker peut être en cause (bugs, surcharge, upstream injoignable, options ndots, etc.).
Vérifier que 127.0.0.11 répond
Dans le conteneur :
dig @127.0.0.11 google.com +time=1 +tries=1
- Si ça répond : le DNS Docker fonctionne au moins localement.
- Si ça timeoute : problème de réseau interne du conteneur, ou du DNS Docker.
Vérifier la résolution des noms de conteneurs/services
Sur un réseau user-defined, Docker résout les noms de conteneurs :
docker network create demo-net
docker run -d --rm --name web --network demo-net nginx
docker run --rm -it --network demo-net nicolaka/netshoot bash
Dans netshoot :
getent hosts web
curl -I http://web
Si web ne se résout pas, c’est un indice que le DNS embarqué ne fonctionne pas correctement sur ce réseau, ou que vous n’êtes pas sur le même réseau.
6) Vérifier le mode réseau Docker et ses implications DNS
Mode bridge (par défaut)
- Conteneur sur
docker0(souvent172.17.0.0/16) - DNS embarqué souvent actif
- NAT vers l’extérieur
Réseau user-defined bridge
- Recommandé pour apps multi-conteneurs
- DNS embarqué + découverte de service
Mode host
Le conteneur partage le réseau de l’hôte :
docker run --rm -it --network host nicolaka/netshoot bash
Dans ce mode, /etc/resolv.conf du conteneur ressemble davantage à celui de l’hôte, et la résolution DNS se comporte souvent comme sur l’hôte. Si en host ça marche mais en bridge ça casse, le problème est probablement :
- NAT/iptables
- accès au DNS depuis le réseau Docker
- DNS embarqué Docker
7) Observer les règles iptables/nftables qui peuvent casser le DNS
Docker manipule le firewall (iptables ou nftables selon la distro). Un firewall trop strict peut bloquer les flux sortants des conteneurs vers UDP/TCP 53.
Vérifier rapidement sur l’hôte
Selon votre système :
sudo iptables -S
sudo iptables -t nat -S
Ou avec nftables :
sudo nft list ruleset
Cherchez des règles qui bloquent :
- le trafic sortant depuis
docker0ou les subnets Docker - UDP 53 / TCP 53
Tester en capturant le trafic DNS
Sur l’hôte, identifiez l’interface Docker (souvent docker0) :
ip link show | grep -E 'docker0|br-'
Puis capture :
sudo tcpdump -ni docker0 port 53
Dans le conteneur, lancez :
dig google.com +time=1 +tries=1
Interprétation :
- Vous voyez des requêtes sortir mais pas de réponses : blocage en chemin (firewall, réseau).
- Vous ne voyez aucune requête : le conteneur n’émet pas (problème local, resolv.conf, libc, application).
Vous pouvez aussi capturer sur l’interface de sortie (ex: eth0) pour voir si les paquets sortent réellement vers le DNS amont.
8) Problèmes liés à ndots et à la recherche de domaines (search domains)
Dans Docker, on voit parfois :
options ndots:0
ou ndots:5 selon les cas. ndots influence quand un nom est considéré “absolu” vs “relatif” (et donc quand la libc tente d’ajouter les search domains). Des valeurs inadaptées peuvent provoquer :
- délais (multiples tentatives sur des suffixes)
- requêtes inutiles
- comportements surprenants sur des noms courts
Vérifier nsswitch.conf
Dans le conteneur :
cat /etc/nsswitch.conf
Recherchez la ligne hosts:. Typiquement :
hosts: files dns
Si dns est absent, getent hosts ne fera pas de DNS. Dans certains conteneurs minimalistes, la configuration peut être non standard.
Exemple de test révélateur
Testez un nom court :
time getent hosts api
time getent hosts api.exemple.com
Si api prend plusieurs secondes, c’est peut-être une recherche de suffixes (search corp.local, etc.) qui timeoute.
9) Différences entre images (Alpine, Debian, distroless) et outils DNS
Alpine (musl)
Alpine utilise musl, dont le comportement DNS peut différer (timeouts, options). Certains problèmes “bizarres” apparaissent seulement sur Alpine.
Outils utiles sur Alpine :
apk add --no-cache bind-tools drill
cat /etc/resolv.conf
drill google.com
Debian/Ubuntu (glibc)
Outils :
apt-get update && apt-get install -y dnsutils iputils-ping netcat-traditional
dig google.com
Distroless / scratch
Vous n’avez pas d’outils. Stratégies :
- lancer un conteneur “debug” sur le même réseau (
--network container:<id>) - ou utiliser
docker execavec un binaire statique si possible
Exemple : attacher un conteneur netshoot au namespace réseau d’un conteneur existant :
docker run --rm -it --network container:<container_id> nicolaka/netshoot bash
Ainsi, vous diagnostiquez exactement le réseau du conteneur problématique.
10) Cas fréquent : DNS d’entreprise / VPN (split DNS)
En entreprise, certains domaines ne sont résolus que via des DNS internes (ex: *.corp.local). Sur l’hôte, le VPN configure des DNS internes, mais Docker ne les récupère pas correctement, ou les requêtes sortent hors VPN.
Indices
dig @8.8.8.8 intranet.corp.localéchoue (normal)- sur l’hôte,
resolvectl statusmontre des DNS par interface (VPN) - dans le conteneur, le DNS amont ne connaît pas les zones internes
Solutions typiques
- Forcer Docker à utiliser les DNS du VPN (IP internes) via
daemon.json. - Utiliser
--dnsau lancement du conteneur. - Assurer que le routage vers les DNS internes passe par le VPN (routes, policy routing).
11) Corriger : configurer explicitement des serveurs DNS pour Docker
Option A : --dns par conteneur (test rapide)
Exemple :
docker run --rm -it --dns 1.1.1.1 --dns 8.8.8.8 nicolaka/netshoot bash
cat /etc/resolv.conf
dig google.com
Avantage : rapide, isolé. Inconvénient : à répéter partout (compose, swarm, etc.).
Option B : configuration globale du daemon Docker
Créez/éditez /etc/docker/daemon.json :
sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
Exemple (DNS publics) :
{
"dns": ["1.1.1.1", "8.8.8.8"]
}
Puis redémarrez Docker :
sudo systemctl restart docker
docker info | sed -n '/DNS/,+3p'
Ensuite, relancez vos conteneurs (les anciens peuvent conserver l’ancienne config).
Option C : Docker Compose
Dans docker-compose.yml, vous pouvez définir :
# Exemple de commande (sans montrer de YAML complet ici)
docker compose run --rm --dns 1.1.1.1 --dns 8.8.8.8 <service> sh
(La configuration exacte dépend de votre fichier compose, mais l’idée est la même : définir des DNS explicites.)
12) Corriger : gérer systemd-resolved proprement
Si votre hôte utilise systemd-resolved, vous pouvez vouloir que Docker utilise le fichier resolv “réel” plutôt que le stub 127.0.0.53.
Vérifier les liens
ls -l /etc/resolv.conf
Si c’est un lien vers ../run/systemd/resolve/stub-resolv.conf, Docker peut récupérer un nameserver 127.0.0.53.
Une approche consiste à pointer /etc/resolv.conf vers /run/systemd/resolve/resolv.conf (attention : impact système, à faire seulement si vous maîtrisez) :
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
Puis :
sudo systemctl restart docker
Alternative plus sûre : définir "dns": [...] dans daemon.json, notamment avec les DNS réellement fournis par le VPN/LAN.
13) Corriger : problèmes de MTU et fragmentation (DNS “flaky”)
Parfois, les requêtes DNS passent, mais certaines réponses (plus grosses, DNSSEC, beaucoup d’enregistrements) se fragmentent et sont perdues. Symptômes :
- certains domaines résolvent, d’autres non
digtimeoute surtout sur des réponses volumineuses- le problème varie selon réseau/VPN
Tester une réponse plus grosse
dig dnssec-failed.org
dig +dnssec org. DNSKEY
Vérifier MTU dans le conteneur et sur l’hôte
Dans le conteneur :
ip link
ip link show eth0
Sur l’hôte :
ip link show docker0
ip link show eth0
Si vous suspectez MTU, vous pouvez tester le PMTU avec ping “do not fragment” (selon permissions) :
ping -M do -s 1472 1.1.1.1
Une correction possible est d’ajuster la MTU du réseau Docker (selon votre environnement), mais c’est plus avancé et dépend de votre infra (VXLAN, VPN, cloud).
14) Déboguer au niveau application : libc, caches, timeouts
Même si le DNS “réseau” est OK, l’application peut avoir :
- un cache DNS interne
- des timeouts agressifs
- une résolution asynchrone (Java, Go, Node) avec comportements spécifiques
- une dépendance à IPv6 (AAAA) qui timeoute
Tester IPv4 vs IPv6
Dans le conteneur :
dig A google.com
dig AAAA google.com
curl -4 https://example.com
curl -6 https://example.com
Si AAAA timeoute et que l’app tente IPv6 d’abord, vous verrez des latences.
Vérifier si IPv6 est activé dans le conteneur
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
ip -6 a
15) Méthode de diagnostic “pas à pas” (checklist)
Voici une séquence efficace, à exécuter dans l’ordre :
- Reproduire avec un conteneur debug sur le même réseau :
docker run --rm -it --network container:<id> nicolaka/netshoot bash - Lire
/etc/resolv.conf:cat /etc/resolv.conf - Tester la résolution via libc :
getent hosts google.com - Tester via
digen passant par le DNS configuré (souvent 127.0.0.11) :dig @127.0.0.11 google.com +time=1 +tries=1 - Tester un DNS public directement :
dig @1.1.1.1 google.com +time=2 +tries=1 - Si timeouts : capturer sur l’hôte :
sudo tcpdump -ni docker0 port 53 - Comparer avec le mode host :
docker run --rm -it --network host nicolaka/netshoot bash dig google.com - Corriger en forçant des DNS (
--dns) puis en global (daemon.json) si nécessaire.
16) Exemples de scénarios réels et solutions
Scénario 1 : “L’hôte résout, le conteneur non” (systemd-resolved)
- Hôte :
/etc/resolv.conf→127.0.0.53 - Conteneur : upstream mal récupéré
- Fix : définir
"dns": ["<dns_lan>", "<dns_vpn>"]dans/etc/docker/daemon.jsonou repointer resolv.conf de l’hôte.
Scénario 2 : “Ça marche parfois” (UDP 53 filtré)
dig @1.1.1.1timeoute en UDPdig @1.1.1.1 +tcpmarche- Fix : ouvrir UDP 53, ou utiliser un DNS autorisé, ou config réseau d’entreprise.
Scénario 3 : “Les noms internes ne résolvent pas dans Docker”
- Split DNS via VPN
- Fix : utiliser les DNS du VPN dans Docker + vérifier routes vers ces DNS.
Scénario 4 : “Les services Docker ne se résolvent pas”
- Conteneurs pas sur le même réseau user-defined
- Fix : créer un réseau dédié et y attacher les services :
docker network create app-net docker network connect app-net <container>
17) Bonnes pratiques pour éviter les pannes DNS en production
- Utiliser des réseaux user-defined (et non le bridge par défaut) pour les stacks multi-conteneurs.
- Définir explicitement des DNS amont fiables (internes ou publics) dans
daemon.jsonquand l’environnement est complexe (VPN, split DNS). - Avoir un conteneur de debug prêt (netshoot) et connaître
--network container:<id>. - Surveiller les changements de DNS côté hôte (VPN qui se connecte/déconnecte).
- Éviter de dépendre de 127.0.0.53 côté conteneurs ; préférer des IP DNS routables.
- Documenter le comportement IPv6 (activé/désactivé) et tester AAAA.
18) Commandes “boîte à outils” (récapitulatif)
Dans un conteneur :
cat /etc/resolv.conf
cat /etc/nsswitch.conf
getent hosts example.com
nslookup example.com
dig example.com
dig @127.0.0.11 example.com
dig @1.1.1.1 example.com
dig @1.1.1.1 example.com +tcp
ip a
ip route
Sur l’hôte :
docker info | sed -n '/DNS/,+3p'
docker inspect <id> --format '{{json .HostConfig.Dns}}'
ls -l /etc/resolv.conf
resolvectl status
sudo tcpdump -ni docker0 port 53
sudo iptables -S
sudo iptables -t nat -S
sudo nft list ruleset
Conclusion
Déboguer le DNS dans Docker revient à remonter la chaîne : application → libc (getent) → /etc/resolv.conf → DNS Docker (127.0.0.11) → DNS amont → réseau/pare-feu/VPN. En procédant par tests simples (résolution via libc, dig via différents serveurs, capture tcpdump, comparaison --network host), vous isolez rapidement la couche fautive.
Si vous me donnez :
- la sortie de
cat /etc/resolv.confdans le conteneur, docker info | sed -n '/DNS/,+3p',- et un
dig @127.0.0.11 example.com +time=1 +tries=1, je peux vous aider à identifier précisément la cause et la correction la plus propre pour votre cas.