Réseau Docker sur un VPS : bridge, host et macvlan expliqués

13 min de lecture·Matthieu·vpsipv6docker-composenetworkingdocker|

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.

Prêt à essayer ?

Maîtrisez le réseau Docker sur un VPS dédié.

Voir les offres VPS