← Retour aux tutoriels

Déboguer les problèmes de résolution DNS dans des conteneurs Docker

dockerdnsdebuggingnetworkingcontainersresolv-confsystemd-resolvediptables

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 :

Sur l’hôte, la résolution peut être gérée par :

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

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

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 :

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

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)

Réseau user-defined bridge

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 :


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 :

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 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 :

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 :

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

Solutions typiques

  1. Forcer Docker à utiliser les DNS du VPN (IP internes) via daemon.json.
  2. Utiliser --dns au lancement du conteneur.
  3. 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 :

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 :

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 :

  1. Reproduire avec un conteneur debug sur le même réseau :
    docker run --rm -it --network container:<id> nicolaka/netshoot bash
  2. Lire /etc/resolv.conf :
    cat /etc/resolv.conf
  3. Tester la résolution via libc :
    getent hosts google.com
  4. Tester via dig en passant par le DNS configuré (souvent 127.0.0.11) :
    dig @127.0.0.11 google.com +time=1 +tries=1
  5. Tester un DNS public directement :
    dig @1.1.1.1 google.com +time=2 +tries=1
  6. Si timeouts : capturer sur l’hôte :
    sudo tcpdump -ni docker0 port 53
  7. Comparer avec le mode host :
    docker run --rm -it --network host nicolaka/netshoot bash
    dig google.com
  8. 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)

Scénario 2 : “Ça marche parfois” (UDP 53 filtré)

Scénario 3 : “Les noms internes ne résolvent pas dans Docker”

Scénario 4 : “Les services Docker ne se résolvent pas”


17) Bonnes pratiques pour éviter les pannes DNS en production


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 :