Vous activez IPv6 dans Docker parce que le produit a besoin « d’adresses Internet réelles », ou parce que l’épuisement d’IPv4 est enfin votre problème.
Les conteneurs démarrent, la plupart des choses fonctionnent… puis vous remarquez du trafic sortant qui contourne votre NAT IPv4 et vos contrôles d’egress soigneusement définis.
Ou pire : un service est accessible en IPv6 depuis des endroits où il ne devrait absolument pas l’être.
L’histoire d’IPv6 dans Docker est praticable, mais ce n’est pas « activer et rentrer chez soi ». Il faut décider : routage IPv6, NAT66 (oui, ça existe),
ou « pas d’egress global sauf autorisation explicite ». Ensuite il faut l’appliquer avec le pare-feu que vous utilisez réellement, pas celui que vous souhaiteriez utiliser.
Ce qui ne va pas avec IPv6 dans Docker (et pourquoi cela surprend des adultes)
Le modèle mental par défaut de Docker est « les conteneurs vivent derrière un NAT IPv4 sur un bridge ». Vous publiez des ports, et l’entrée fonctionne. La sortie fonctionne.
Vous définissez quelques règles iptables, et vous avez l’impression de contrôler la situation.
IPv6 change le contrat. Si votre hôte dispose d’IPv6 global et que vous donnez des IPv6 globales (ou routables globalement) aux conteneurs, ils ne sont plus
nécessairement derrière un NAT. Cela signifie :
- Le contrôle d’egress peut échouer silencieusement. Vos contrôles IPv4 ne s’appliquent pas à IPv6. Le conteneur parle joyeusement au monde en v6.
- L’exposition d’entrée peut vous surprendre. Si vous autorisez accidentellement le forwarding (ou si Docker insère des règles permissives), les conteneurs peuvent devenir accessibles.
- Des lacunes d’observabilité apparaissent. Les journaux de flux, les journaux de proxy et les tableaux de bord « quelle IP avons-nous utilisée » supposent souvent IPv4.
- Le DNS peut contourner les politiques. Les réponses AAAA + Happy Eyeballs peuvent choisir IPv6 même quand vous « aviez l’intention » d’IPv4.
Une autre subtilité : Docker a plusieurs endroits où IPv6 peut être « activé », et ils ne signifient pas tous la même chose.
Il y a l’IPv6 au niveau du daemon, l’IPv6 par réseau, des pools d’adresses, et puis ce que fait réellement votre noyau et votre pare-feu.
Quand quelqu’un dit « IPv6 est activé », demandez : « activé où, et avec quelle politique d’egress ? »
Faits et contexte historique (pour calibrer vos intuitions)
Quelques faits concrets et brefs aident à éviter de grosses hypothèses coûteuses :
- IPv6 a été normalisé à la fin des années 1990 (ère RFC 2460) en grande partie parce que l’épuisement d’IPv4 était une catastrophe annoncée.
- Le NAT n’a jamais été la fonctionnalité de sécurité que l’on prétendait. Il masquait des hôtes, mais ne remplaçait pas un pare-feu ; IPv6 supprime cette « couverture de confort » accidentelle.
- Le vaste espace d’adresses IPv6 a changé la logique d’allocation. L’objectif était de restaurer l’adressage de bout en bout et de réduire la traduction d’adresses d’état.
- Happy Eyeballs existe parce que le déploiement d’IPv6 était inégal. Les clients essaient IPv6 et IPv4 en parallèle pour éviter de ralentir l’utilisateur quand un chemin est défaillant.
- Docker s’appuyait à l’origine sur le comportement d’iptables. À mesure que les systèmes migrent vers des backends nftables et différentes distributions, la « magie » est devenue plus fragile.
- Linux gère IPv6 depuis des décennies, mais les sysctls par défaut et le comportement des RA varient selon la distribution et l’image cloud.
- NAT66 existe mais est controversé. Il répond à certains besoins de transition et de politique, mais ce n’est pas le « chemin heureux » d’IPv6.
- Beaucoup de réseaux d’entreprise bloquent encore l’IPv6 entrant par politique tout en autorisant l’IPv6 sortant, ce qui explique exactement comment surviennent les « fuites surprises ».
- L’IPv6 dans le cloud est souvent routé, pas bridgé. Vous recevez fréquemment un /64 (ou plus) routé vers une interface ; votre travail est de router proprement les préfixes vers les réseaux des conteneurs.
Un modèle mental sain : L2 en bridge, L3 routé, et la « fuite » que vous n’avez pas voulue
Le bridge par défaut de Docker est plutôt L2 sur l’hôte : les conteneurs obtiennent une interface dans un bridge Linux, et l’hôte effectue le routage/forwarding L3.
Avec IPv4, Docker SNATe généralement le trafic sortant vers l’IP de l’hôte (MASQUERADE). Ainsi le monde voit l’hôte, pas le conteneur.
Avec IPv6, vous avez des options. Si vous attribuez des IPv6 globales routables aux conteneurs et que vous routez correctement ce préfixe, le monde peut voir les adresses des conteneurs.
C’est acceptable — même souhaitable — si c’est ce que vous voulez et si vous configurez le pare-feu en conséquence.
C’est un problème lorsque les « contrôles » de votre organisation ont été implémentés comme :
- « N’autoriser la sortie que via notre proxy IPv4. »
- « N’autoriser l’entrée que sur les ports publiés. »
- « Notre équipe de sécurité surveille les journaux de la passerelle NAT. »
La fuite est simple : IPv6 devient un deuxième chemin Internet moins gouverné. Votre conteneur peut résoudre des enregistrements AAAA et se connecter directement.
Pas de proxy. Pas de NAT. Pas de journaux là où vous vous y attendiez.
Une citation à garder sur votre mur, non pas parce qu’elle est poétique, mais parce qu’elle est vraie en opération :
Idée paraphrasée : « L’espoir n’est pas une stratégie. »
— attribuée à divers ingénieurs et responsables des opérations.
Choix d’architecture : IPv6 routé vs NAT66 vs pas de global par défaut
Décidez ce que vous voulez. Si vous ne le faites pas, votre environnement décidera pour vous, généralement durant une réunion d’incident.
Option A : IPv6 routé (recommandé quand c’est faisable proprement)
Les conteneurs reçoivent des adresses depuis un préfixe que vous contrôlez. Vous routez ce préfixe depuis votre routeur amont (ou réseau cloud) vers l’hôte Docker,
puis l’hôte route vers les bridges des conteneurs. Pas de NAT. Communication de bout en bout claire. Facile à raisonner une fois que vous acceptez que le pare-feu est obligatoire.
Avantages : vrai IPv6, pas d’état de traduction, meilleure transparence, possibilité de gérer l’entrée avec pare-feu + services publiés.
Inconvénients : nécessite délégation de préfixe ou sous-réseau routé ; vous devez implémenter le pare-feu IPv6 intentionnellement.
Option B : NAT66 (acceptable comme palliatif politique, pas comme style de vie)
Vous pouvez garder la posture « les conteneurs sont cachés derrière l’hôte » en NATant l’egress IPv6.
Ce n’est pas du pur IPv6, mais c’est un point de contrôle pragmatique quand le routage amont est difficile ou quand la politique exige une identité d’egress unique.
Avantages : conserve une identité d’egress stable, réduit l’exposition entrante par défaut.
Inconvénients : introduit un état de traduction, casse les hypothèses de bout en bout, et peut compliquer le dépannage.
Option C : Pas d’IPv6 globale pour les conteneurs (ULA seulement + egress contrôlé)
Donnez aux conteneurs seulement des ULA (fd00::/8) en interne, et forcez l’egress via un proxy ou une passerelle que vous contrôlez.
Cela s’aligne sur « les conteneurs ne doivent pas parler à Internet sauf autorisation explicite ».
Avantages : contrôle par défaut le plus strict, moindre surprise d’exposition.
Inconvénients : vous devrez de toute façon construire ou exploiter une passerelle ; certaines applications s’attendent à un IPv6 direct.
Choisissez-en un et consignez-le. Faites-en une invariance de la plateforme. Sinon chaque équipe inventera son propre « juste cette fois » IPv6.
Activer IPv6 correctement : daemon, réseaux et plan d’adressage
Il y a deux grandes façons dont les gens se font piéger :
- Ils activent l’IPv6 dans Docker mais ne planifient pas le préfixe, si bien que la sélection d’adresses devient accidentelle et incohérente entre les hôtes.
- Ils planifient le préfixe mais oublient le pare-feu, si bien que le « IPv6 routé » devient un « zoo public de conteneurs ».
Plan d’adressage qui ne vous hantera pas
Vous voulez des préfixes stables et non chevauchants par hôte Docker ou par segment de cluster. Dans un design IPv6 routé, un schéma courant est :
- Allouer un préfixe plus large à votre environnement (par ex. un /56 ou /48 fourni par votre opérateur).
- Attribuer un /64 par réseau bridge Docker (car SLAAC et beaucoup de piles supposent des frontières en /64).
- Router ces /64 vers l’hôte, puis laisser Docker attribuer des adresses à l’intérieur.
Si vous ne pouvez pas obtenir d’espace routable, l’ULA peut fonctionner en interne, mais soyez honnête : l’ULA ne vous donne pas magiquement la connectivité Internet.
Vous aurez toujours besoin de NAT66 ou d’un proxy/passerelle.
Les réglages du daemon Docker qui comptent
L’activation d’IPv6 dans Docker commence typiquement dans /etc/docker/daemon.json. Les réglages exacts dépendent de vos objectifs :
"ipv6": trueactive le support IPv6 pour le bridge par défaut."fixed-cidr-v6": "2001:db8:.../64"(préfixe d’exemple) assigne un sous-réseau IPv6 fixe au réseau bridge par défaut."ip6tables": trueindique à Docker de gérer les règles de pare-feu IPv6. Cela peut aider, mais ne déléguez pas votre politique au daemon.
Si vous exécutez plusieurs réseaux bridge définis par l’utilisateur, vous utiliserez probablement docker network create --ipv6 avec des sous-réseaux explicites plutôt que de compter uniquement sur le bridge par défaut.
Blague n°1 : IPv6, c’est comme emménager dans une maison avec beaucoup plus de pièces — super jusqu’à ce que vous réalisiez que vous avez oublié d’installer des portes.
Pare-feu pour IPv6 Docker : ne comptez plus sur les impressions
Votre politique doit exister dans le pare-feu de l’hôte, pas dans un tableau. Avec IPv6, l’hôte est un routeur. Traitez-le comme tel.
Cela signifie :
- Politique par défaut drop explicite sur le forwarding, puis autorisez ce que vous entendez.
- Politique d’egress explicite (surtout si vous exécutez des proxies, des contrôles DLP ou des passerelles d’audit).
- Journalisation aux bons endroits pour que « pourquoi ça ne se connecte pas » ne devienne pas un projet d’archéologie de deux heures.
Réalité iptables vs nftables
De nombreuses distributions utilisent maintenant nftables en interne même lorsque vous tapez iptables. Docker gérait historiquement les règles iptables directement.
Cela peut mener à des comportements « ça marchait l’année dernière, maintenant non » après des mises à jour.
Votre travail : savoir quel backend de pare-feu est actif et tester les règles avec du trafic réel.
Si vous utilisez nftables explicitement, écrivez des règles nftables. Ne comptez pas sur Docker pour vous garder en sécurité.
Prévenir les fuites IPv6 surprises
La fuite la plus courante est : l’egress IPv4 passe par votre NAT/proxy ; l’egress IPv6 passe directement.
La correction est tout aussi simple et tout aussi ignorée : implémentez des contrôles IPv6 sortants sur le chemin de forwarding de l’hôte.
Si votre politique est « les conteneurs doivent utiliser un proxy », alors votre pare-feu doit bloquer l’egress IPv6 direct sauf vers le proxy.
Si votre politique est « les conteneurs peuvent parler vers l’extérieur », alors autorisez-le, mais assurez-vous de pouvoir l’observer et de le segmenter/limiter si nécessaire.
DNS et Happy Eyeballs : où naît le « ça marche sur mon portable »
Une fois IPv6 disponible, le DNS renvoie des enregistrements AAAA. Beaucoup de clients préféreront IPv6 ou feront une course entre IPv6 et IPv4.
Cela signifie que « nous avons bloqué IPv4 vers cette destination » n’est pas équivalent à « nous avons bloqué la destination ».
Vérifiez vos résolveurs. Vérifiez vos images de base de conteneur. Certaines images contiennent des ajustements de resolv.conf, ou ont un comportement libc différent.
Si vous exécutez un DNS interne qui synthétise des AAAA (DNS64), vous obtiendrez un egress IPv6 même quand le service en amont est uniquement IPv4.
Le point opérationnel : diagnostiquez la connectivité sur les chemins A et AAAA, et ne considérez pas un « ping qui marche » comme une preuve de la reachabilité applicative.
Tâches pratiques : commandes, sorties et décisions (12+)
Voici les tâches que j’exécute réellement quand j’active IPv6 dans Docker ou que je le débogue à 2 h du matin.
Chacune inclut : la commande, un exemple de sortie, ce que cela signifie et la décision à prendre.
Task 1: Confirm the host actually has IPv6 and a default route
cr0x@server:~$ ip -6 addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet6 2001:db8:10:20::15/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a00:27ff:fe4e:66a1/64 scope link
valid_lft forever preferred_lft forever
Signification : Vous avez une IPv6 globale et une link-local. Bon début.
Décision : S’il n’y a pas d’IPv6 globale, arrêtez. Vous ne pouvez pas faire d’IPv6 routé sans support amont. Envisagez ULA + passerelle ou corrigez d’abord l’IPv6 de l’hôte.
cr0x@server:~$ ip -6 route show default
default via 2001:db8:10:20::1 dev eth0 metric 100
Signification : L’hôte a une route IPv6 par défaut.
Décision : Si la route par défaut manque, les conteneurs échoueront en sortie même si des adresses existent. Corrigez le routage amont/RA/routes statiques avant d’accuser Docker.
Task 2: Check kernel forwarding and IPv6 sysctls
cr0x@server:~$ sysctl net.ipv6.conf.all.forwarding net.ipv6.conf.default.forwarding
net.ipv6.conf.all.forwarding = 0
net.ipv6.conf.default.forwarding = 0
Signification : L’hôte ne transfère pas de paquets IPv6. Les conteneurs peuvent avoir IPv6 mais ne routeront pas via l’hôte.
Décision : Pour un IPv6 routé, mettez forwarding=1 (et assurez-vous de la politique du pare-feu). Si vous visez « pas de forwarding », alors c’est correct — faites appliquer l’egress autrement.
Task 3: Inspect Docker daemon IPv6 configuration
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"ipv6": true,
"fixed-cidr-v6": "2001:db8:dead:beef::/64",
"ip6tables": true
}
Signification : IPv6 Docker est activé sur le bridge par défaut avec un /64 fixe, et Docker gérera les règles iptables IPv6.
Décision : Validez que ce /64 est routé vers l’hôte (ou au moins joignable). S’il est aléatoire ou chevauche un autre endroit, corrigez le plan avant de redémarrer Docker.
Task 4: Restart Docker and confirm it applied the config
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ systemctl status docker --no-pager -l
● docker.service - Docker Application Container Engine
Loaded: loaded (/lib/systemd/system/docker.service; enabled)
Active: active (running) since Tue 2026-01-02 09:18:31 UTC; 6s ago
Docs: https://docs.docker.com
Main PID: 1462 (dockerd)
Tasks: 19
Memory: 76.3M
CGroup: /system.slice/docker.service
└─1462 /usr/bin/dockerd -H fd://
Signification : Docker a redémarré correctement.
Décision : S’il ne démarre pas, consultez les journaux du journal pour des erreurs de syntaxe JSON ou des options non prises en charge par votre version de Docker.
Task 5: Verify the default bridge has an IPv6 subnet
cr0x@server:~$ docker network inspect bridge --format '{{json .IPAM.Config}}'
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"},{"Subnet":"2001:db8:dead:beef::/64","Gateway":"2001:db8:dead:beef::1"}]
Signification : Le bridge par défaut a maintenant un /64 IPv6 et une adresse de gateway.
Décision : Si le sous-réseau IPv6 est absent, la config du daemon n’est pas appliquée ou IPv6 est désactivé. Corrigez avant d’aller plus loin.
Task 6: Create a user-defined network with explicit IPv6
cr0x@server:~$ docker network create --driver bridge --ipv6 --subnet 2001:db8:dead:cafe::/64 appv6
a1d8d3f6a2e7b2c1c0c9b5c0d74a2c1c4f6a8a1c2e1f0b9e8d7c6b5a4f3e2d1
Signification : Vous avez maintenant un réseau bridge activé IPv6 avec un /64 explicite. C’est généralement plus facile à gérer que le bridge par défaut.
Décision : Si votre organisation veut de la segmentation par application, créez un réseau par stack et appliquez une politique de pare-feu par interface de bridge.
Task 7: Run a container and confirm it gets IPv6
cr0x@server:~$ docker run --rm --network appv6 alpine sh -c "ip -6 addr show dev eth0"
27: eth0@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
inet6 2001:db8:dead:cafe::2/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe11:2/64 scope link
valid_lft forever preferred_lft forever
Signification : Le conteneur a une IPv6 globale issue de votre sous-réseau et une adresse link-local.
Décision : S’il n’a que des link-local, IPv6 Docker n’est pas correctement activé pour ce réseau ou la configuration du sous-réseau n’a pas été appliquée.
Task 8: Test outbound IPv6 connectivity from the container
cr0x@server:~$ docker run --rm --network appv6 alpine sh -c "apk add --no-cache curl >/dev/null; curl -6 -sS -m 3 https://ifconfig.co/ip"
2001:db8:10:20::15
Signification : L’egress IPv6 fonctionne, et l’egress apparaît comme l’adresse de l’hôte (dans cet exemple). Selon le routage/NAT, cela peut afficher l’IP du conteneur ou de l’hôte.
Décision : Si la politique exige l’egress via un proxy, cet IPv6 direct est une fuite. Bloquez-le ou routez-le explicitement via le proxy.
Task 9: See whether Docker installed IPv6 firewall rules (and whether they’re sane)
cr0x@server:~$ sudo ip6tables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -j RETURN
Signification : DOCKER-USER existe mais est vide (RETURN). Docker n’imposera pas votre politique par défaut.
Décision : Mettez votre politique d’egress/ingress des conteneurs dans DOCKER-USER (ou l’équivalent nftables) pour qu’elle survive aux règles dynamiques de Docker.
Task 10: Check whether forwarding is allowed by firewall (nftables path)
cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif "lo" accept
ct state established,related accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Signification : Default-drop sur forward. Bonne base : rien ne transite sauf autorisation. Docker peut quand même insérer des règles ailleurs selon la configuration.
Décision : Ajoutez des autorisations explicites pour les interfaces bridge Docker vers les destinations voulues. Si votre chaîne forward est accept par défaut, attendez-vous à une « exposition surprise ».
Task 11: Confirm which interfaces Docker created and map them to networks
cr0x@server:~$ ip link show type bridge
5: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
8: br-7b3b9c2f7a12: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500
Signification : docker0 (bridge par défaut) et un bridge défini par l’utilisateur (br-…).
Décision : Utilisez des règles de pare-feu par bridge. Si vous regroupez tout dans une seule liste d’autorisations, vous perdez la segmentation et la clarté pour le dépannage.
Task 12: Verify routes to the container subnet exist on the host
cr0x@server:~$ ip -6 route show | grep -E 'dead:beef|dead:cafe'
2001:db8:dead:beef::/64 dev docker0 proto kernel metric 256
2001:db8:dead:cafe::/64 dev br-7b3b9c2f7a12 proto kernel metric 256
Signification : L’hôte a des routes connectées vers les /64 des conteneurs via les bridges Docker.
Décision : Si ces routes manquent, la config réseau Docker est erronée ou le bridge n’est pas actif. Corrigez avant de toucher au routage amont.
Task 13: Check upstream reachability to the container subnet (routing is not optional)
cr0x@server:~$ ping6 -c 2 2001:db8:dead:cafe::2
PING 2001:db8:dead:cafe::2(2001:db8:dead:cafe::2) 56 data bytes
64 bytes from 2001:db8:dead:cafe::2: icmp_seq=1 ttl=64 time=0.120 ms
64 bytes from 2001:db8:dead:cafe::2: icmp_seq=2 ttl=64 time=0.118 ms
--- 2001:db8:dead:cafe::2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1010ms
Signification : De l’hôte vers le conteneur c’est bon (local). Cela ne prouve pas que le monde extérieur peut atteindre le sous-réseau des conteneurs.
Décision : Si vous avez besoin d’entrée vers les conteneurs, assurez-vous que votre routeur amont a des routes pour ces /64 pointant vers l’hôte.
Task 14: Detect “IPv6 bypasses proxy” in one minute
cr0x@server:~$ docker run --rm --network appv6 alpine sh -c "apk add --no-cache bind-tools curl >/dev/null; nslookup example.com | sed -n '1,12p'"
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
Name: example.com
Address: 93.184.216.34
Name: example.com
Address: 2606:2800:220:1:248:1893:25c8:1946
Signification : Le conteneur voit A et AAAA. Beaucoup de clients utiliseront IPv6 si cela fonctionne.
Décision : Si votre posture de sécurité suppose le proxy/NAT pour la journalisation, implémentez maintenant les contrôles d’egress IPv6, pas après qu’un audit demande pourquoi des données ont quitté les contrôles.
Blague n°2 : Si vous ne firewallisez pas IPv6, vous exécutez pratiquement de la production avec la porte-écran en mode « démonstration ».
Playbook de diagnostic rapide
Quand IPv6 dans les conteneurs est cassé (ou trop « fonctionnel »), vous voulez un chemin court vers la vérité.
Voici l’ordre qui trouve rapidement le goulot d’étranglement.
Première étape : santé IPv6 au niveau de l’hôte (ne commencez pas dans le conteneur)
- L’hôte a-t-il une adresse IPv6 globale sur l’interface montante ?
- Y a-t-il une route IPv6 par défaut ?
- Le forwarding IPv6 est-il activé si vous routez le trafic des conteneurs ?
Si l’IPv6 de l’hôte est instable, l’IPv6 des conteneurs n’est que du théâtre.
Deuxième étape : plomberie du réseau Docker
- Le réseau Docker a-t-il un sous-réseau IPv6 et une gateway ?
- Le conteneur obtient-il une adresse IPv6 globale/ULA sur eth0 ?
- L’hôte a-t-il une route connectée vers ce sous-réseau via le bridge ?
Troisième étape : pare-feu et politique (le coupable habituel)
- Les paquets IPv6 forwardés sont-ils autorisés depuis le bridge du conteneur vers la liaison montante ?
- ICMPv6 est-il bloqué incorrectement (ce qui casse PMTU, la découverte de voisins et la cohérence) ?
- Votre politique d’egress est-elle implémentée pour IPv6, ou seulement pour IPv4 ?
Quatrième étape : DNS et comportement applicatif
- Le DNS renvoie-t-il des AAAA ? L’application utilise-t-elle IPv6 de manière inattendue ?
- Y a-t-il du DNS64 ou un résolveur interne qui réécrit les réponses ?
- Observez-vous un comportement différent avec
curl -4vscurl -6?
Cinquième étape : routage amont (uniquement si vous avez besoin d’entrée)
- Les routes pour les /64 des conteneurs sont-elles présentes sur le routeur amont/table de routage cloud ?
- Le filtrage de chemin inverse ou l’anti-usurpation bloque-t-il le trafic ?
Erreurs courantes : symptôme → cause racine → correction
Ce ne sont pas des « théories ». Ce sont celles qui apparaissent dans les timelines d’incidents.
1) Les conteneurs ont des adresses IPv6 mais ne peuvent rien atteindre en IPv6
Symptôme : curl -6 expire ; le DNS montre AAAA ; IPv4 fonctionne.
Cause racine : forwarding IPv6 de l’hôte désactivé, ou chaîne forward du pare-feu qui droppe l’IPv6.
Correction : Activez net.ipv6.conf.all.forwarding=1 pour les designs routés et autorisez le forwarding pour l’interface bridge Docker ; gardez default-drop mais ajoutez des autorisations spécifiques.
2) L’egress IPv6 contourne le proxy/NAT de l’entreprise
Symptôme : L’équipe sécurité voit du trafic vers des destinations IPv6 externes non visible dans les journaux NAT IPv4.
Cause racine : pas de politique d’egress IPv6 ; les clients préfèrent IPv6 à cause des AAAA + Happy Eyeballs.
Correction : Bloquez l’egress IPv6 direct depuis les bridges des conteneurs sauf vers les passerelles/proxies approuvés, ou forcez l’utilisation du proxy ; validez avec des tests curl -6.
3) Un conteneur est joignable depuis Internet en IPv6 sans ports publiés
Symptôme : Un scan externe atteint un service écoutant sur l’IPv6 du conteneur ; l’équipe jure ne pas l’avoir publié.
Cause racine : IPv6 routé + règles de forwarding/input permissives autorisent l’entrée ; Docker ne « cache » pas les services comme le faisait le NAT en IPv4.
Correction : Default-drop sur l’IPv6 entrant/forwardé ; autorisez explicitement seulement les ports requis vers des conteneurs spécifiques ; envisagez des réseaux séparés pour les charges publiques.
4) Ça marche sur un hôte mais pas sur un autre
Symptôme : Même fichier compose, comportements différents selon les nœuds.
Cause racine : backend de pare-feu différent (iptables vs nft), sysctls différents, ou provisioning IPv6 amont différent (certains nœuds ont des routes v6, d’autres non).
Correction : Standardisez la base hôte : sysctls, outil de pare-feu, daemon.json, et validation de provisioning IPv6 dans la CI pour les images de nœuds.
5) Interruptions aléatoires et problèmes MTU étranges en IPv6
Symptôme : Gros transferts bloquent ; petites requêtes réussissent ; logs montrent des retransmissions.
Cause racine : ICMPv6 bloqué (PMTU discovery cassé), ou discordance MTU sur overlay/underlay.
Correction : Autorisez les types ICMPv6 essentiels ; vérifiez le MTU du chemin ; évitez de « bloquer tout ICMP » comme on le lisait sur des blogs de 2004.
6) « On a activé IPv6 » mais seules des adresses link-local existent
Symptôme : Le conteneur n’affiche que des adresses fe80::.
Cause racine : Réseau non créé avec --ipv6 ou IPv6 au niveau du daemon non activé ; fixed-cidr-v6 manquant pour le bridge par défaut.
Correction : Activez IPv6 dans le daemon et/ou par réseau ; recréez les réseaux au besoin (prudemment, avec plan de maintenance).
Trois mini-récits d’entreprise depuis les tranchées IPv6
Mini-récit 1 : L’incident causé par une mauvaise hypothèse
Une entreprise SaaS de taille moyenne a migré un service d’ingestion très sollicité en conteneurs sur une flotte d’hôtes Linux. L’egress IPv4 était strictement contrôlé :
tout le trafic sortant des workloads passait par une passerelle NAT surveillée et une couche de proxy. L’audit appréciait. Les finances appréciaient. Tout le monde dormait.
L’équipe plateforme a activé IPv6 sur les hôtes parce qu’une nouvelle API partenaire était « IPv6-first », et ils voulaient éviter une couche supplémentaire de traduction.
Ils ont activé le paramètre IPv6 de Docker, validé que les conteneurs pouvaient atteindre le partenaire en IPv6, puis sont passés à autre chose.
Deux semaines plus tard, la sécurité a signalé des connexions sortantes inhabituelles vers des destinations externes qui n’apparaissaient pas dans les journaux de la passerelle NAT.
Ce n’était pas un malware. C’était de la télémétrie d’application normale, directe vers des points de terminaison fournisseurs, choisissant IPv6 parce que le fournisseur avait des enregistrements AAAA.
Personne n’avait décidé si la télémétrie pouvait contourner le proxy. Elle l’a fait.
La réponse à l’incident fut délicate car rien n’avait été « piraté ». Le système faisait exactement ce pour quoi il était configuré.
La mauvaise hypothèse était culturelle : les gens pensaient que « NAT équivaut à contrôle d’egress ». IPv6 a supprimé le NAT, et donc l’illusion.
La correction fut simple mais politiquement délicate : ils ont implémenté des règles d’egress IPv6 explicites sur les hôtes Docker et forcé HTTP(S) sortant
via un proxy contrôlé, pour IPv4 et IPv6. La leçon : la politique réseau doit être explicite par famille de protocoles.
Mini-récit 2 : L’optimisation qui s’est retournée contre eux
Une grande entreprise disposait d’une plateforme de conteneurs où IPv6 était « supporté », mais uniquement de façon limitée : ULA en interne, NAT66 à la périphérie.
L’équipe réseau voulait réduire l’état et simplifier le dépannage, aussi a-t-elle proposé « IPv6 routé partout ».
Ça paraît propre. Ça l’est, quand on le fait délibérément.
Ils ont déployé des /64 routés par hôte et autorisé largement le forwarding parce qu’ils ne voulaient pas casser les workloads.
Le plan était de resserrer les règles du pare-feu plus tard, après observation des patterns de trafic.
Ce « plus tard » n’a jamais été planifié. C’était juste une humeur.
En un mois, un scan de vulnérabilité a trouvé plusieurs UI d’administration destinées à l’interne accessibles en IPv6 depuis des réseaux adjacents.
Elles n’étaient pas « exposées sur l’Internet public », mais elles étaient exposées au-delà du segment prévu, suffisant pour déclencher une alerte de conformité.
Les services n’avaient jamais été joignables en IPv4 à cause du NAT et d’un défaut-de-nier dans cette direction, donc les propriétaires ne les considéraient pas risqués.
L’échec n’était pas l’IPv6 routé. L’IPv6 routé était la bonne architecture. L’échec était le « pare-feu permissif temporaire ».
L’optimisation sans garde-fous, c’est juste foncer vers un post-mortem.
Ils ont fini par faire le travail qu’ils auraient dû faire en premier : default-drop sur le forwarding, autorisations explicites par réseau et port, et une séparation formelle « public vs privé ».
L’IPv6 routé est resté. L’habitude du « on durcira plus tard » ne l’a pas survécue.
Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une entreprise proche des paiements faisait tourner des workloads conteneurisés avec un contrôle des changements strict. Pas amusant, mais efficace.
Quand ils ont commencé à ajouter IPv6, ils ont rédigé une norme d’une page : plan d’adressage, allocation de préfixe par hôte, et invariants de pare-feu.
Chaque construction d’hôte validait les mêmes sysctls et règles de pare-feu. Chaque réseau Docker était créé explicitement avec un /64 connu.
Des mois plus tard, une mise à jour d’image cloud a modifié subtilement le comportement du pare-feu : la distribution est passée à des règles nftables avec une politique par défaut différente.
Sur une plateforme moins disciplinée, c’est là que l’IPv6 aurait soit cessé de fonctionner silencieusement, soit se serait ouvert silencieusement.
Ici, les tests de validation de l’hôte ont échoué pendant le bake : les conteneurs ont perdu l’egress IPv6 parce que les règles de forwarding n’étaient pas présentes.
La correction fut ennuyeuse : mettre à jour le modèle de règles nftables, relancer la validation, rebuilder.
Pas d’incident. Pas de changement d’urgence. Pas de « pourquoi seul IPv6 casse ? »
La pratique qui les a sauvés était aussi la moins glamour : traiter IPv6 comme un premier citoyen dans les tests de configuration de base.
Si vous ne testez que l’IPv4 dans votre pipeline d’images dorées, vous choisissez d’être surpris plus tard.
Checklists / plan étape par étape
Voici le plan que j’utiliserais pour déployer IPv6 Docker en production là où « oups » coûte cher.
Choisissez d’abord le design, puis exécutez.
Checklist A: Pré-vol (hôte et amont)
- Confirmez que l’hôte a une IPv6 globale stable et une route par défaut.
- Décidez : IPv6 routé, NAT66 ou ULA-only.
- Obtenez ou allouez un plan de préfixes (par hôte ou par réseau). Évitez les chevauchements entre environnements.
- Réglez les sysctls : activez le forwarding IPv6 si vous routez les conteneurs ; assurez-vous que le comportement RA correspond à votre environnement.
- Définissez une baseline de pare-feu : default-drop sur le forwarding ; autorisez les ICMPv6 nécessaires ; décidez des contrôles sortants.
Checklist B: Configuration Docker
- Configurez
/etc/docker/daemon.jsonavec"ipv6": trueet un"fixed-cidr-v6"si vous utilisez le bridge par défaut. - Préférez des réseaux définis par l’utilisateur avec des sous-réseaux IPv6 explicites pour les applications.
- Redémarrez Docker lors d’une fenêtre de maintenance si cela impacte des workloads en cours.
- Validez que
docker network inspectmontre le sous-réseau/gateway IPv6.
Checklist C: Application de la politique (éviter les fuites)
- Implémentez une politique d’egress IPv6 : autorisez seulement ce que vous entendez (proxy, destinations spécifiques, ou sortie complète).
- Implémentez une politique d’ingress IPv6 : default-drop, autorisez seulement les services publiés et assurez-vous que les sous-réseaux des conteneurs ne sont pas largement joignables.
- Journalisez les drops de façon limitante en débit pour déboguer sans transformer votre disque en brasier.
- Testez le comportement induit par AAAA : comparez
curl -4etcurl -6depuis les conteneurs.
Checklist D: Gates de validation avant déploiement
- Connectivité : conteneur vers Internet en IPv6 (si autorisé) et vers les dépendances internes.
- Test de fuite : confirmez que l’egress IPv6 direct est bloqué si la politique exige un proxy.
- Test d’exposition : confirmez que les conteneurs ne sont pas joignables en IPv6 sauf si prévu.
- Observabilité : confirmez que les logs/métriques incluent IPv6 (parsing IP client, tableaux de bord, alertes).
- Test de mise à niveau : assurez-vous que les changements d’outils de pare-feu (iptables/nftables) sont détectés dans la validation d’image.
FAQ
1) Dois-je activer IPv6 globalement dans Docker ou par réseau ?
Par réseau, avec des sous-réseaux explicites, est généralement plus propre. Activez au niveau du daemon pour le permettre, puis créez des réseaux définis avec --ipv6.
Vous vous remercierez quand vous aurez besoin de segmentation et d’adressage prévisible.
2) Ai-je besoin d’un /64 pour chaque réseau Docker ?
Pratiquement oui si vous voulez éviter des surprises. Beaucoup d’outils et d’hypothèses IPv6 tournent autour du /64. Des préfixes plus petits peuvent fonctionner dans certains designs routés,
mais vous passerez votre temps à lutter contre des cas limites plutôt qu’à faire tourner des services.
3) Le NAT66 est-il « mauvais » ?
Ce n’est pas une faute morale. C’est un compromis. Si votre routage amont est contraint ou si vous avez besoin d’une identité d’egress unique, NAT66 peut être pragmatique.
Documentez-le et attendez-vous à une complexité de dépannage accrue.
4) Pourquoi mes contrôles d’egress ont-ils cessé de fonctionner après l’activation d’IPv6 ?
Parce que vos contrôles étaient spécifiques à IPv4 (journaux de la passerelle NAT, règles pare-feu IPv4, application de proxy IPv4). IPv6 a créé un second chemin.
Corrigez en appliquant explicitement la politique pour IPv6 — ne comptez pas sur l’espoir que les clients restent en IPv4.
5) Les conteneurs peuvent-ils être joignables en IPv6 sans publier de ports ?
Oui, dans les designs IPv6 routés, si votre pare-feu l’autorise et si le routage existe. Le NAT IPv4 masquait souvent cette réalité.
Avec IPv6, supposez qu’une socket en écoute est joignable à moins que vous ne la bloquiez.
6) Dois-je autoriser ICMPv6 ? Ne puis-je pas simplement le bloquer comme on le faisait pour ICMP ?
Vous avez besoin d’ICMPv6 essentiel pour la découverte de voisins et la découverte PMTU. Surbloquer ICMPv6 entraîne des échecs intermittents et étranges qui gaspillent des week-ends.
Autorisez des types spécifiques ; ne faites pas de drop global.
7) Pourquoi curl utilise parfois IPv6 alors qu’IPv4 fonctionne ?
Le DNS renvoie des enregistrements AAAA, et les bibliothèques clientes préfèrent souvent IPv6 ou font une course IPv6/IPv4 (Happy Eyeballs).
Si IPv6 est disponible et non bloqué, il sera utilisé. Considérez cela comme un comportement attendu, pas une trahison.
8) Comment prévenir les fuites IPv6 sans tout casser ?
Commencez par bloquer l’IPv6 sortant depuis les interfaces bridge des conteneurs sauf vers des passerelles connues (proxy, miroirs de mise à jour si nécessaire).
Puis ajoutez des exceptions intentionnellement. Validez avec curl -6 et des vérifications DNS AAAA.
9) Docker Compose prend-il en charge les réseaux IPv6 ?
Oui, via la définition de réseaux avec IPv6 activé et des sous-réseaux explicites. L’important est de s’assurer que le daemon et l’hôte supportent IPv6 et que votre politique de pare-feu est cohérente.
10) Quelle est la preuve la plus rapide qu’un conteneur « s’échappe » via IPv6 ?
Résolvez un domaine dual-stack connu et forcez IPv6 : utilisez nslookup pour voir AAAA, puis curl -6 vers un point public.
S’il réussit alors que vos contrôles IPv4 auraient bloqué l’accès, vous avez une lacune dans la politique d’egress.
Conclusion : étapes suivantes qui ne vous feront pas honte dans deux trimestres
Activer IPv6 dans Docker n’est pas difficile. L’activer en toute sécurité demande de la discipline.
Le travail n’est pas majoritairement « Docker ». Il s’agit de routage et de politique : plan d’adressage, forwarding, pare-feu, comportement DNS et observabilité.
Étapes pratiques suivantes :
- Notez votre choix d’architecture (IPv6 routé, NAT66 ou ULA-only) et la posture de sécurité associée.
- Implémentez des contrôles de base sur les hôtes : adresse IPv6, route par défaut, sysctls de forwarding et cohérence du backend de pare-feu.
- Créez des réseaux Docker IPv6 explicites avec des /64 connus ; cessez de laisser les « valeurs par défaut » définir l’architecture.
- Appliquez la politique d’egress IPv6 sur le chemin de forwarding pour que l’IPv6 ne puisse pas contourner vos contrôles.
- Ajoutez des validations dans votre pipeline d’images : tests
curl -6, vérifications DNS AAAA et un scan d’exposition minimal en staging.
Faites cela, et IPv6 deviendra juste un autre transport que votre plateforme supporte — ennuyeux, prévisible, et pas une voie latérale surprise vers Internet.