Pourquoi deux conteneurs sur le même hôte ne se voient pas par défaut

Lancez Postgres et votre API avec un simple docker run, et l’API n’arrive pas à joindre la base de données, alors que les deux conteneurs tournent sur la même machine.

1
2
docker run -d --name db postgres:16
docker run -d --name api myapp

Les deux atterrissent sur la bridge par défaut de Docker, celle que reçoit tout conteneur Docker sauf indication contraire. Ils obtiennent une adresse IP. Ce qu’ils n’obtiennent pas, c’est un moyen de se trouver par leur nom : l’API devrait donc coder en dur l’IP interne de la base de données, une adresse qui change à chaque redémarrage du conteneur.

Un réseau Docker règle ce problème. C’est un switch virtuel auquel les conteneurs se rattachent, avec sa propre plage d’IP et son propre DNS. En créer un transforme les noms de conteneurs en hostnames, et vous décidez qui voit qui.

Quel driver réseau utiliser : bridge, host ou overlay

Docker embarque plusieurs drivers réseau. Trois d’entre eux couvrent presque tout ce que vous croiserez sur un seul hôte ou sur un swarm :

DriverPortéeLes conteneurs ont leur propre IPDNS par nomUsage typique
bridge (par défaut)un seul hôteouinon (IP uniquement)ce que vous obtenez sans le flag --network
bridge (user-defined)un seul hôteouiouile choix par défaut pour le dev local et les déploiements mono-hôte
hostun seul hôtenon, partage la pile réseau de l’hôten/aservices à fort débit, outils qui ont besoin de voir le réseau réel de l’hôte
overlayplusieurs hôtes (swarm)ouiouiservices répartis sur plusieurs hôtes Docker

Il existe aussi none, qui donne au conteneur une simple interface de loopback et rien d’autre. Utile pour un job batch qui n’a rien à faire sur le réseau.

Chaque nouveau docker run atterrit sur la bridge par défaut. Ça marche, mais elle route uniquement par IP ; l’ancien flag --link servait à combler cette absence de noms, et Docker le marque comme legacy. Une bridge user-defined fait le même travail avec le DNS intégré. C’est celle qu’il vous faut.

Comment créer un réseau Docker et y attacher des conteneurs

1
docker network create app-net

C’est une bridge privée avec son propre sous-réseau, isolée de la bridge par défaut et de tout autre réseau user-defined que vous créez. Attachez les conteneurs au démarrage :

1
2
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=devpass postgres:16
docker run -d --name api --network app-net -p 8080:8080 myapp

Depuis api, se connecter à db:5432 fonctionne. Pas de recherche d’IP, pas de jonglage avec des variables d’environnement : le serveur DNS intégré de Docker résout le nom de conteneur db vers son adresse actuelle sur app-net, et met à jour cette correspondance quand le conteneur redémarre avec une nouvelle IP. Docker Compose repose sur le même mécanisme. docker compose up crée un réseau pour le stack, et chaque service joint les autres par son nom de service.

Vous pouvez aussi attacher un conteneur déjà en cours d’exécution, sans le redémarrer :

1
2
docker network connect app-net some-container
docker network disconnect app-net some-container

Un conteneur peut se trouver sur plusieurs réseaux à la fois. C’est ainsi que vous donnez à un service un réseau exposé publiquement et un réseau privé partagé uniquement avec les conteneurs backend derrière lui.

Pourquoi publier un port n’est pas la façon dont deux conteneurs communiquent

-p 8080:8080 mappe le port 8080 de l’hôte sur celui du conteneur, ce qui vous permet d’atteindre l’API depuis votre navigateur sur localhost:8080. Cela ne détermine pas si api arrive à joindre db. Le trafic entre conteneurs sur le même réseau user-defined reste interne, ports publiés ou non.

Ne publiez que les ports qu’un service extérieur à l’hôte Docker doit atteindre. Une base de données qui ne parle qu’à l’API n’a pas besoin de -p 5432:5432 : laissez-la non publiée, et ni l’hôte ni internet ne pourront s’y connecter directement. api continue de la joindre, puisqu’ils partagent app-net.

Quand utiliser –network host, et quand l’éviter

1
docker run --rm -d --network host --name my_nginx nginx

Avec --network host, le conteneur saute son propre namespace réseau et se lie directement aux interfaces de l’hôte. Pas de NAT, pas de bridge, pas de mapping de ports : -p et -P sont ignorés, et Docker affiche un avertissement si vous les passez quand même. Cela réduit un peu la surcharge par paquet et donne au conteneur la vue réelle des interfaces réseau de l’hôte, ce que recherchent les outils de monitoring réseau et les logiciels ayant besoin d’une large plage de ports contiguë.

Cela supprime aussi l’isolation qu’apporte une bridge. Le conteneur peut se lier à n’importe quel port libre de l’hôte, et voit tout ce qui transite sur le réseau de l’hôte. Sous Linux, c’est pris en charge sans restriction. Docker Desktop pour Mac et Windows exige de l’activer explicitement (disponible depuis Docker Desktop 4.34), parce que le réseau de la VM Docker n’est pas celui de l’hôte. Les conteneurs Windows ne le prennent pas en charge du tout. Pour la plupart des services, une bridge user-defined avec des ports publiés fait le même travail avec moins d’exposition.

Quand il vous faut un réseau overlay plutôt qu’une bridge

Les réseaux bridge et host s’arrêtent à la frontière d’un seul hôte Docker. Faites tourner Docker Swarm sur plusieurs machines, et un service sur l’hôte A a besoin d’un réseau overlay pour joindre un service sur l’hôte B :

1
docker network create -d overlay --attachable app-overlay

Une overlay encapsule le trafic des conteneurs et le route entre les daemons Docker de chaque nœud du swarm, si bien que des conteneurs sur des machines physiques ou virtuelles différentes se joignent par nom exactement comme sur une bridge. Le flag --attachable permet aussi à des conteneurs standalone de la rejoindre ; sans lui, seuls les services swarm le peuvent. Si vous ne faites pas tourner swarm ou un autre orchestrateur multi-hôte, laissez ce driver de côté. Un réseau bridge mono-hôte couvre les stacks Compose et les configurations locales.

Comment inspecter et déboguer un réseau Docker

1
docker network ls

Tous les réseaux présents sur l’hôte, y compris bridge, host et none, que Docker crée toujours.

1
docker network inspect app-net

Le sous-réseau, la passerelle, et chaque conteneur actuellement attaché avec son IP sur ce réseau. C’est le premier endroit à regarder quand un conteneur n’arrive pas à en joindre un autre. Lisez la section Containers : si l’un d’eux manque, il n’est pas sur ce réseau, et docker network connect l’y place.

Pour tester la résolution DNS depuis l’intérieur d’un conteneur en cours d’exécution :

1
docker exec api getent hosts db

Recevoir une IP signifie que la résolution de noms fonctionne et que le problème vient de l’application : mauvais port, ou service pas encore à l’écoute. Ne rien recevoir signifie que les deux conteneurs ne sont pas sur le même réseau. Confirmez-le avec docker network inspect, ou demandez à chaque conteneur à quoi il est attaché :

1
docker inspect <container> --format '{{ .NetworkSettings.Networks }}'

Un réseau par stack, avec tous ses conteneurs dessus

La bridge par défaut, vous l’avez gratuitement. Ce n’est pas sur elle que vous devez construire. Lancez docker network create <app>-net pour chaque stack applicatif, attachez-y tous les conteneurs dont ce stack a besoin, et réservez --network host au service rare qui a vraiment besoin de la pile réseau de l’hôte.

Dès que vous gérez plus de deux conteneurs à la main, arrêtez de le faire à la main. Docker Compose crée le réseau et y attache les conteneurs pour vous, depuis un seul fichier YAML. Les volumes Docker couvrent l’autre moitié du problème pour survivre à un redémarrage : les données que ces conteneurs écrivent.

Articles associés