Réseau Docker sur un VPS : bridge, host et macvlan expliqués
Comment fonctionnent les réseaux bridge, host et macvlan de Docker sur un VPS. Résolution DNS sur bridge personnalisé, publication de ports, configuration IPv6 et isolation réseau avec Docker Compose.
Vous avez un VPS. Plusieurs conteneurs doivent communiquer entre eux et avec le monde extérieur. Docker propose trois pilotes réseau (network drivers) pertinents ici : bridge, host et macvlan. Chacun fait des compromis différents entre isolation, performance et praticité.
Cet article explique quand choisir chaque pilote, comment relier vos conteneurs avec des réseaux bridge personnalisés et Docker Compose, et comment éviter les erreurs courantes qui rendent des services inaccessibles ou accidentellement exposés.
Prérequis : connaissances de base du CLI Docker et familiarité avec Docker Compose.
Quelle est la différence entre les réseaux bridge et host de Docker ?
Le réseau bridge attribue à chaque conteneur son propre espace de noms réseau (network namespace) avec une paire ethernet virtuelle (veth) le reliant à un bridge logiciel sur l'hôte. Les conteneurs reçoivent des IP privées (typiquement dans la plage 172.17.0.0/16) et accèdent à l'extérieur via du NAT géré par iptables. Le réseau host supprime l'espace de noms réseau : le conteneur partage directement la pile réseau de l'hôte, se liant aux ports sans traduction d'adresse.
Voici une comparaison rapide :
| Critère | Bridge personnalisé | Host | Macvlan |
|---|---|---|---|
| Isolation réseau | Complète (namespace propre) | Aucune (partage l'hôte) | Complète (adresse MAC propre) |
| Résolution DNS | Automatique par nom de conteneur | Utilise le DNS de l'hôte | Pas de DNS intégré |
| Mappage de ports | Requis (-p) |
Inutile (liaison directe) | Inutile (IP réelle) |
| Surcoût en performance | Faible (NAT + veth) | Aucun | Aucun |
| Risque de sécurité | Bas (isolé par défaut) | Élevé (pas de frontière réseau) | Moyen (mode promiscuous requis) |
| Pertinence VPS | Choix principal | Monitoring, haut débit | Rarement utilisable |
Pourquoi utiliser un réseau bridge personnalisé plutôt que le bridge par défaut ?
Le réseau bridge par défaut (appelé docker0) ne fournit pas de résolution DNS entre conteneurs. Les conteneurs ne peuvent s'atteindre que par adresse IP, qui change à chaque redémarrage. Les réseaux bridge personnalisés offrent le DNS automatique, une meilleure isolation et la possibilité de connecter/déconnecter des conteneurs à chaud. Utilisez un bridge personnalisé pour tout. Traitez le bridge par défaut comme un vestige.
Le problème du DNS
Quand Docker démarre, il crée un réseau bridge par défaut. Chaque conteneur sans flag --network explicite atterrit dessus. Mais les conteneurs sur le bridge par défaut ne peuvent pas se résoudre par nom :
# Start two containers on the default bridge
docker run -d --name web nginx:alpine
docker run -d --name client alpine sleep 3600
# Try name resolution - this fails
docker exec client ping -c1 web
# ping: bad address 'web'
Essayez la même chose avec un bridge personnalisé :
# Create a custom bridge network
docker network create app-net
# Start containers on it
docker run -d --name web2 --network app-net nginx:alpine
docker run -d --name client2 --network app-net alpine sleep 3600
# Name resolution works
docker exec client2 ping -c1 web2
# PING web2 (172.18.0.2): 56 data bytes
# 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.089 ms
Comment fonctionne le DNS embarqué de Docker
Chaque conteneur sur un réseau défini par l'utilisateur reçoit 127.0.0.11 comme serveur DNS. C'est le serveur DNS embarqué de Docker. Il résout les noms de conteneurs et les alias de service vers leurs adresses IP actuelles. Si le nom ne correspond pas à un conteneur, il transmet la requête aux serveurs DNS configurés sur l'hôte.
docker exec client2 cat /etc/resolv.conf
# nameserver 127.0.0.11
Le serveur DNS tourne dans l'espace de noms réseau du conteneur, mais la résolution effective se fait dans le daemon Docker sur l'hôte. Pour éviter les conflits avec des services qui utiliseraient le port 53 dans le conteneur, le listener DNS de Docker utilise un port haut aléatoire en interne et redirige les requêtes via des règles iptables.
Il n'existe pas d'adresse IPv6 équivalente. L'adresse 127.0.0.11 fonctionne même dans les conteneurs IPv6 uniquement.
Le serveur DNS embarqué renvoie un TTL de 600 secondes (10 minutes) pour les enregistrements de noms de conteneurs. C'est important pour les déploiements blue-green : si vous remplacez un conteneur, les autres peuvent encore résoudre l'ancienne IP pendant 10 minutes. Les applications qui mettent agressivement le DNS en cache (Java met en cache indéfiniment par défaut) conserveront les adresses obsolètes encore plus longtemps.
Les conteneurs sur le même réseau bridge personnalisé exposent tous leurs ports les uns aux autres sans aucun flag -p. La publication de ports ne contrôle que l'accès depuis l'extérieur du réseau (l'hôte ou l'internet). Deux conteneurs sur app-net communiquent librement sur n'importe quel port.
Bridge par défaut vs bridge personnalisé
| Fonctionnalité | Bridge par défaut (docker0) |
Bridge personnalisé |
|---|---|---|
| DNS par nom de conteneur | Non | Oui |
| Connexion/déconnexion dynamique | Non (redémarrage du conteneur requis) | Oui |
| Configurable par réseau | Non (nécessite modification de daemon.json + redémarrage) |
Oui |
| Isolation par rapport aux autres stacks | Non (tous les conteneurs non assignés le partagent) | Oui |
| Recommandé en production | Non | Oui |
Quand utiliser le réseau host sur un VPS ?
Utilisez le réseau host quand un conteneur a besoin de performances réseau brutes ou doit se lier dynamiquement à de nombreux ports. Le conteneur partage directement la pile réseau de l'hôte, contournant le NAT et le bridge ethernet virtuel. Le surcoût mesurable pour les charges à haut débit est éliminé.
Cas d'usage typiques sur un VPS :
- Agents de monitoring comme Prometheus node-exporter ou Netdata qui doivent voir toutes les interfaces et le trafic de l'hôte
- Serveurs DNS qui doivent écouter sur l'IP réelle de l'hôte
- Services sensibles aux performances où le surcoût du NAT compte (taux de paquets élevé, charges UDP intensives)
# Run a container with host networking
docker run -d --name node-exporter --network host \
prom/node-exporter:latest
Le flag --publish est ignoré avec le réseau host. Le conteneur se lie directement aux ports de l'hôte. Si le port 9100 est déjà utilisé sur l'hôte, le conteneur ne démarre pas.
Compromis de sécurité
Le réseau host offre la même isolation réseau que d'exécuter le processus directement sur l'hôte : aucune. Le conteneur peut voir toutes les interfaces réseau, se lier à n'importe quel port et capturer le trafic. Ne l'utilisez que si vous avez une raison précise.
# Verify which ports a host-networked container opened
ss -tlnp | grep node_exporter
# LISTEN 0 4096 *:9100 *:* users:(("node_exporter",pid=12345,fd=3))
cette sortie montre que le processus écoute sur *:9100, c'est-à-dire toutes les interfaces. Avec le réseau bridge, vous contrôlez cela via le flag -p. Avec le réseau host, c'est l'application elle-même qui décide sur quoi se lier.
Qu'est-ce que le réseau macvlan et en avez-vous besoin sur un VPS ?
Le macvlan assigne à chaque conteneur sa propre adresse MAC, le faisant apparaître comme un appareil physique distinct sur le réseau. Le conteneur obtient une IP réelle de votre sous-réseau LAN et est joignable directement sans mappage de ports.
Sur un VPS, vous n'avez presque certainement pas besoin de macvlan. Voici pourquoi :
- La plupart des fournisseurs cloud le bloquent. Le macvlan nécessite que la carte réseau physique fonctionne en mode promiscuous. Les hyperviseurs VPS bloquent généralement cela au niveau du switch virtuel.
- Pas de LAN à rejoindre. Votre VPS a une seule interface publique. Le macvlan est conçu pour les environnements où vous voulez que vos conteneurs aient leurs propres adresses sur un LAN existant, comme un lab domestique avec des objets connectés ou des applications legacy qui attendent une IP dédiée.
- La communication hôte-conteneur est cassée. Par conception, le noyau Linux empêche un conteneur macvlan de communiquer avec l'hôte sur lequel il tourne. Il faut des contournements comme attacher un second réseau bridge.
Si votre fournisseur VPS vous donne un serveur dédié avec un accès NIC complet et plusieurs IP, le macvlan devient viable. Pour les déploiements VPS standard, restez sur les réseaux bridge personnalisés.
Pour référence, la création d'un réseau macvlan ressemble à ceci (vous n'en aurez probablement pas besoin sur un VPS) :
docker network create -d macvlan \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
-o parent=eth0 \
macnet
L'option parent spécifie l'interface hôte à laquelle s'attacher. Le conteneur obtient sa propre IP dans le sous-réseau et sa propre adresse MAC. Les autres appareils sur le LAN peuvent le joindre directement.
Quelle est la différence entre exposer et publier un port Docker ?
EXPOSE dans un Dockerfile est de la documentation. Il déclare le port sur lequel l'application écoute. Il n'ouvre pas ce port vers l'hôte ou l'extérieur. --publish (ou -p) au runtime crée un vrai mappage de port entre l'hôte et le conteneur. Sans -p, aucun trafic externe n'atteint le conteneur.
# In your Dockerfile - this is documentation only
EXPOSE 8080
# This actually maps host:8080 -> container:8080
docker run -d -p 8080:8080 myapp
# This binds to localhost only - external traffic cannot reach it
docker run -d -p 127.0.0.1:8080:8080 myapp
Le pattern de liaison 127.0.0.1
Quand vous exécutez des services derrière un reverse proxy comme Nginx, Traefik ou Caddy , publiez les ports des conteneurs sur 127.0.0.1 uniquement. Cela empêche l'accès direct depuis internet, forçant tout le trafic à passer par votre reverse proxy où se font la terminaison TLS, la limitation de débit et le contrôle d'accès.
# docker-compose.yml - binding to localhost only
services:
app:
image: myapp:latest
ports:
- "127.0.0.1:3000:3000" # Only reachable from localhost
Vérifiez la liaison :
ss -tlnp | grep 3000
# LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:* users:(("docker-proxy",...))
la sortie affiche 127.0.0.1:3000, pas 0.0.0.0:3000. le trafic externe sur l'IP publique de votre VPS ne peut pas atteindre le port 3000 directement.
Sans le préfixe 127.0.0.1, Docker publie sur toutes les interfaces par défaut. Sur un VPS avec une IP publique, votre service est exposé sur internet, contournant votre reverse proxy et potentiellement votre pare-feu.
Référence rapide de publication de ports
| Syntaxe | Se lie à | Accessible depuis |
|---|---|---|
-p 8080:80 |
0.0.0.0:8080 + [::]:8080 |
Partout (IPv4 + IPv6) |
-p 127.0.0.1:8080:80 |
127.0.0.1:8080 |
Localhost uniquement |
-p 0.0.0.0:8080:80 |
0.0.0.0:8080 |
Toutes les interfaces IPv4 |
-p [::1]:8080:80 |
[::1]:8080 |
Localhost IPv6 uniquement |
Comment isoler les réseaux de conteneurs sur un seul VPS ?
Créez des réseaux bridge séparés pour chaque pile applicative. Les conteneurs sur des réseaux différents ne peuvent pas communiquer sauf si vous les connectez explicitement à un réseau partagé. Pour les bases de données et services internes, utilisez le flag internal: true pour bloquer tout accès internet sortant.
Architecture multi-réseau
Une configuration de production typique sur un VPS ressemble à ceci :
Internet
|
[Reverse Proxy]
/ \
[frontend] [api-net]
| |
webapp api-server
|
[db-net: internal]
|
postgres
Le reverse proxy est connecté à frontend et api-net. Le serveur API est connecté à api-net et db-net. PostgreSQL ne se trouve que sur db-net, sans route vers internet.
Voici le fichier Docker Compose pour cette configuration :
services:
proxy:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
networks:
- frontend
- api-net
webapp:
image: mywebapp:latest
networks:
- frontend
api:
image: myapi:latest
environment:
- DATABASE_URL=postgresql://appuser:${DB_PASSWORD}@db:5432/appdb
networks:
- api-net
- db-net
db:
image: postgres:16-alpine
environment:
- POSTGRES_PASSWORD_FILE=/run/secrets/db_password
volumes:
- pgdata:/var/lib/postgresql/data
networks:
- db-net
networks:
frontend:
driver: bridge
api-net:
driver: bridge
db-net:
driver: bridge
internal: true # No internet access for the database network
volumes:
pgdata:
Le internal: true sur db-net signifie que PostgreSQL ne peut pas atteindre internet. Il ne peut pas télécharger de mises à jour, contacter un serveur distant, ni être exploité comme pivot pour des connexions sortantes. Le serveur API peut atteindre la base de données car il est attaché à api-net et db-net.
Vérifier l'isolation réseau
Après avoir démarré la pile, confirmez l'isolation :
# List networks created by Compose
docker network ls --filter "name=myproject"
# Inspect a network to see which containers are connected
docker network inspect myproject_db-net --format '{{range .Containers}}{{.Name}} {{end}}'
# myproject-api-1 myproject-db-1
# Confirm the database cannot reach the internet
docker exec myproject-db-1 ping -c1 -W2 8.8.8.8
# ping: sendto: Network unreachable
« Network unreachable » signifie que le flag internal: true fonctionne. Le conteneur de base de données n'a pas de passerelle par défaut.
Comment activer IPv6 pour les conteneurs Docker ?
Activez IPv6 globalement dans /etc/docker/daemon.json, puis activez-le par réseau avec un sous-réseau. Docker prend en charge le dual-stack (IPv4 + IPv6) nativement sous Linux. Le paramètre ip6tables, activé par défaut, gère les règles de pare-feu IPv6 pour les conteneurs.
Étape 1 : configurer le daemon
Éditez /etc/docker/daemon.json :
{
"ipv6": true,
"fixed-cidr-v6": "fd00:dead:beef::/64",
"ip6tables": true,
"default-address-pools": [
{ "base": "172.17.0.0/16", "size": 24 },
{ "base": "fd00:dead:beef::/48", "size": 64 }
]
}
Le fixed-cidr-v6 assigne un sous-réseau IPv6 /64 au bridge par défaut. Le bloc default-address-pools indique à Docker comment allouer les sous-réseaux pour les nouveaux réseaux : des blocs /24 dans la plage IPv4 et des blocs /64 dans la plage IPv6.
Utilisez un préfixe ULA (fd00::/8) pour le trafic privé conteneur-à-conteneur. Si votre VPS dispose d'une plage IPv6 publique attribuée par votre fournisseur, vous pouvez utiliser un sous-réseau de cette plage pour les conteneurs qui ont besoin d'adresses IPv6 publiques.
Redémarrez Docker pour appliquer :
sudo systemctl restart docker
Vérifiez qu'IPv6 est activé :
docker network inspect bridge --format '{{.EnableIPv6}}'
# true
Étape 2 : créer un réseau dual-stack
docker network create --ipv6 --subnet 172.20.0.0/24 --subnet fd00:dead:beef:1::/64 app-v6
Ou dans Docker Compose :
networks:
app-v6:
enable_ipv6: true
ipam:
config:
- subnet: 172.20.0.0/24
- subnet: fd00:dead:beef:1::/64
Étape 3 : vérifier la connectivité dual-stack
docker run --rm --network app-v6 alpine ip -6 addr show eth0
# inet6 fd00:dead:beef:1::2/64 scope global
IPv6 n'est pris en charge que sur les daemons Docker tournant sous Linux. Sur les systèmes plus anciens, vous devrez peut-être charger le module noyau ip6_tables avant de créer des réseaux IPv6 :
sudo modprobe ip6_tables
Comment définir des réseaux dans Docker Compose ?
Docker Compose crée un réseau par défaut pour chaque projet automatiquement. Chaque service du fichier Compose rejoint ce réseau et peut résoudre les autres services par leur nom de service. Pour la plupart des applications mono-pile, ce comportement par défaut suffit.
Quand vous avez besoin de plusieurs réseaux pour l'isolation, définissez-les explicitement dans la section networks :
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
networks:
- public
app:
image: node:20-alpine
networks:
- public
- private
redis:
image: redis:7-alpine
networks:
- private
networks:
public:
driver: bridge
private:
driver: bridge
internal: true
Dans ce fichier, web peut joindre app via le réseau public. app peut joindre web et redis. redis ne peut joindre que app via private et n'a pas d'accès internet.
Utiliser le réseau host dans Compose
services:
node-exporter:
image: prom/node-exporter:latest
network_mode: host
pid: host
restart: unless-stopped
Le network_mode: host remplace toute définition networks. Vous ne pouvez pas combiner le mode host avec des réseaux personnalisés sur le même service.
Se connecter à des réseaux externes
Si un réseau a été créé en dehors de Compose (par une autre pile ou manuellement), référencez-le comme externe :
networks:
shared-proxy:
external: true
Cela permet à plusieurs projets Compose de partager un réseau. Un pattern courant : une pile Compose fait tourner le reverse proxy et crée le réseau. Les autres piles le déclarent comme externe et y connectent leurs services.
Référence rapide des commandes réseau Docker
| Commande | Action |
|---|---|
docker network ls |
Lister tous les réseaux |
docker network create mynet |
Créer un réseau bridge |
docker network create --ipv6 --subnet fd00::/64 mynet |
Créer un réseau dual-stack |
docker network inspect mynet |
Afficher les détails du réseau (sous-réseau, conteneurs, options) |
docker network connect mynet container1 |
Attacher un conteneur en cours d'exécution à un réseau |
docker network disconnect mynet container1 |
Détacher un conteneur d'un réseau |
docker network prune |
Supprimer tous les réseaux inutilisés |
docker network rm mynet |
Supprimer un réseau spécifique |
Limites de scalabilité
Les réseaux bridge deviennent instables au-delà de 1 000 conteneurs connectés à un seul réseau. C'est une limitation du noyau Linux sur le bridge device. Si vous faites tourner beaucoup de conteneurs, répartissez-les sur plusieurs réseaux par fonction plutôt que de tout mettre sur un seul bridge.
Quelque chose ne marche pas ?
Les conteneurs ne peuvent pas se résoudre par nom.
Vous êtes probablement sur le bridge par défaut. Créez un réseau personnalisé et attachez-y les deux conteneurs. Vérifiez avec docker inspect <container> --format '{{json .NetworkSettings.Networks}}'.
Le port est publié mais inaccessible depuis l'extérieur.
Vérifiez s'il est lié à 127.0.0.1 au lieu de 0.0.0.0. Lancez ss -tlnp | grep <port> sur l'hôte. Vérifiez aussi vos règles de pare-feu.
Le conteneur ne peut pas accéder à internet.
Le réseau a peut-être internal: true. Vérifiez avec docker network inspect <network> --format '{{.Internal}}'. Si la réponse est true, le réseau bloque le trafic sortant par conception.
IPv6 ne fonctionne pas dans les conteneurs.
Vérifiez que ip6tables est activé dans daemon.json. Sur les noyaux anciens, chargez le module : sudo modprobe ip6_tables. Vérifiez que le réseau a IPv6 activé : docker network inspect <network> --format '{{.EnableIPv6}}'.
« Address already in use » avec le réseau host.
Un autre processus (ou conteneur) est déjà lié à ce port. Trouvez-le avec ss -tlnp | grep <port>. Le réseau host ne fait pas de mappage de ports, donc les conflits sont directs.
Prochaines étapes
Votre réseau de conteneurs est en place. Voici la suite :
- Ajouter un reverse proxy pour gérer TLS et le routage
- Corriger le contournement du pare-feu par Docker pour que les ports publiés respectent vos règles UFW/nftables
- Ajouter des limites de ressources et des healthchecks à vos services Compose
- Revenir à la vue d'ensemble Docker sur VPS pour la checklist de production complète
Copyright 2026 Virtua.Cloud. Tous droits réservés. Ce contenu est une création originale de l'équipe Virtua.Cloud. Toute reproduction, republication ou redistribution sans autorisation écrite est interdite.