Docker vs iptables/nftables : mettre fin à la guerre des pare-feu et réparer le réseau

Cet article vous a aidé ?

Ça commence toujours de la même manière : « les conteneurs n’accèdent plus à Internet », « le port 443 n’est plus publié », ou le classique « ça marchait hier ». Vous vérifiez Docker : il tourne. Vous vérifiez l’application : elle est saine. Puis vous regardez le pare‑feu et découvrez que vous ne déboguez pas « le réseau ». Vous déboguez deux moteurs de pare‑feu qui pensent chacun qu’ils commandent.

Sur les distributions modernes Linux, iptables peut être un shim de compatibilité au‑dessus de nftables. Ou il peut s’agir du binaire legacy. Docker parle encore iptables. Votre distribution peut préférer nftables. Ajoutez un gestionnaire de pare‑feu qui recharge les règles au moindre changement, et vous avez une guerre de production entièrement menée dans des tables noyau.

Ce qui se passe réellement quand Docker « casse le réseau »

Le modèle réseau par défaut de Docker sur un hôte unique est trompeusement simple :

  • Créer un bridge Linux (généralement docker0).
  • Attacher des paires veth des conteneurs à ce bridge.
  • Faire du NAT (MASQUERADE) pour que les conteneurs atteignent l’extérieur.
  • Ajouter des règles de filtrage pour que le forwarding fonctionne et que les ports publiés arrivent dans le bon conteneur.

Chacune de ces étapes touche netfilter. Le problème, c’est que « iptables » n’est pas qu’un outil ; c’est aussi une interface vers les tables noyau. Et ces dernières années, l’outil en espace utilisateur a joué aux chaises musicales :

  • iptables-legacy : parle à l’ancienne interface xtables.
  • iptables-nft : la commande iptables qui écrit des règles dans nftables (via une couche de compatibilité).
  • nft : l’outil natif nftables et son langage.

Si Docker écrit des règles avec un backend et que votre système inspecte ou gère l’autre backend, vous « ne voyez rien », pensez que rien n’est configuré, puis « corrigez » cela d’une façon qui empire la situation.

Il y a aussi trois couches de politique qui se confrontent régulièrement :

  1. Paramètres noyau comme net.ipv4.ip_forward et les sysctls bridge netfilter.
  2. Gestionnaires de pare‑feu (firewalld, ufw, unités systemd personnalisées, gestion de configuration) qui définissent une politique par défaut et rechargent les règles.
  3. La gestion des règles par Docker, qui suppose pouvoir insérer des chaînes comme DOCKER et DOCKER-USER puis y sauter.

Les modes de défaillance sont constants :

  • Les règles NAT existent dans un jeu de règles, mais les paquets sont évalués par un autre.
  • Le forwarding est bloqué parce que la politique par défaut est DROP, ou parce qu’un rechargement du pare‑feu a oublié les sauts vers Docker.
  • Les ports publiés ne fonctionnent pas parce que les règles DNAT manquent ou que la chaîne DOCKER n’est pas référencée.
  • Le trafic inter‑conteneurs meurt parce que le filtrage du bridge est activé et que vous ne l’avez pas autorisé.

Choisissez un camp. Rendez‑le stable. Ne laissez plus trois outils griffonner dans le même carnet.

Une petite blague promise : le NAT, c’est comme la politique de bureau : tout marche jusqu’à ce que quelqu’un « simplifie » les règles et que soudain plus personne ne peut parler à personne.

Playbook de diagnostic rapide (premières/deuxièmes/troisièmes vérifications)

Première étape : confirmer le backend du pare‑feu et où les règles sont écrites

  • Est‑ce que iptables utilise nft ou legacy ?
  • Est‑ce que nft list ruleset affiche des chaînes/règles Docker ?
  • Inspectez‑vous le même backend que celui que Docker programme ?

Deuxième étape : confirmer que le forwarding et le NAT existent pour le bridge Docker

  • net.ipv4.ip_forward=1 ?
  • Règle MASQUERADE pour le sous‑réseau docker0 ?
  • La chaîne FORWARD autorise‑t‑elle docker0 → externe et le trafic de retour ?

Troisième étape : trouver qui réécrit les règles après le démarrage de Docker

  • firewalld se recharge ? ufw s’active ? scripts de durcissement personnalisés ?
  • Ordonnancement systemd : Docker démarre‑t‑il avant/après votre service de pare‑feu ?
  • Les règles disparaissent‑elles après un rechargement du pare‑feu ?

Quand vous êtes sous pression

Si c’est en production et que le site est indisponible, le chemin le plus sûr et le plus rapide est généralement :

  1. S’assurer que Docker et votre hôte utilisent le même backend (souvent en sélectionnant iptables‑legacy ou en alignant tout sur nftables).
  2. Restaurer le forwarding + NAT et confirmer avec les compteurs de paquets.
  3. Empêcher les futurs effacements de règles en corrigeant l’ordre des services et en utilisant DOCKER-USER pour votre politique.

Ensuite vous revenez en profondeur et vous rendez la solution correcte, pas seulement « fonctionnelle maintenant ».

Faits intéressants et courte histoire (pour comprendre les bizarreries)

  • nftables est arrivé dans Linux 3.13 (2014) comme successeur d’iptables, offrant un jeu de règles plus flexible et de meilleures performances pour de grands nombres de règles.
  • iptables-nft est une couche de compatibilité, pas « iptables avec une sortie différente ». Il traduit les règles iptables en objets nftables, et la traduction a des cas limites.
  • Certaines distributions ont basculé iptables vers nft par défaut (via alternatives), ce qui a changé silencieusement la signification de iptables -S sur la même ligne de commande.
  • Le modèle réseau de Docker est antérieur à nftables, et ses hypothèses opérationnelles ont été conçues autour des chaînes iptables qu’il peut insérer et gérer de façon incrémentale.
  • nftables prend en charge des mises à jour atomiques du jeu de règles, ce qui est excellent pour la cohérence ; cela signifie aussi qu’un outil peut remplacer l’ensemble du jeu de règles d’un coup et supprimer accidentellement les chaînes Docker.
  • firewalld a évolué vers nftables, mais de nombreux environnements utilisent encore des outils mixtes où Docker utilise iptables et firewalld utilise nftables nativement.
  • La chaîne DOCKER-USER existe pour une raison : Docker avait besoin d’un point d’insertion stable pour la politique utilisateur afin qu’elle ne soit pas effacée par les propres mises à jour de Docker.
  • Bridge netfilter est un piège courant : les paquets traversant les bridges Linux peuvent être envoyés à iptables/nftables selon les sysctls, ce qui change le chemin que vos règles doivent correspondre.
  • Les échecs de réseau de conteneurs sont souvent des « échecs de politique » et non des « échecs de routage » : les routes semblent correctes, l’ARP fonctionne, mais netfilter bloque silencieusement ou ne fait pas de NAT.

Tâches pratiques : commandes, sorties et décisions

Voici les vérifications que j’exécute quand le graphique est rouge et que Slack est bruyant. Chaque tâche inclut : une commande, une sortie réaliste, ce que ça signifie, et la décision que vous prenez.

Task 1: Identify which iptables backend you’re using

cr0x@server:~$ sudo iptables --version
iptables v1.8.9 (nf_tables)

Ce que ça signifie : Votre commande iptables écrit dans nftables (iptables‑nft). Si Docker utilise le même binaire, ses règles résident dans nftables, pas dans xtables legacy.

Décision : Vous devez inspecter avec nft ou utiliser iptables de manière cohérente. Si un autre composant utilise iptables‑legacy, vous avez un split‑brain.

Task 2: Check alternatives (Debian/Ubuntu) for iptables and friends

cr0x@server:~$ sudo update-alternatives --display iptables
iptables - auto mode
  link best version is /usr/sbin/iptables-nft
  link currently points to /usr/sbin/iptables-nft
  link iptables is /usr/sbin/iptables
/usr/sbin/iptables-legacy - priority 10
/usr/sbin/iptables-nft - priority 20

Ce que ça signifie : La préférence système est iptables soutenu par nft. Vos outils et attentes doivent correspondre à cela.

Décision : Si vous trouvez que Docker ou votre pare‑feu utilise legacy, alignez‑les (soit migrez tout vers nft, soit basculez délibérément vers legacy pour la simplicité liée à l’époque Docker).

Task 3: Confirm Docker’s bridge and subnets

cr0x@server:~$ docker network inspect bridge --format '{{json .IPAM.Config}}'
[{"Subnet":"172.17.0.0/16","Gateway":"172.17.0.1"}]

Ce que ça signifie : Le sous‑réseau du bridge par défaut est 172.17.0.0/16. Les règles NAT et de forwarding doivent référencer ce CIDR ou cette interface.

Décision : Si vous utilisez des sous‑réseaux personnalisés, vous devez les voir ici et vérifier que le pare‑feu correspond. Des sous‑réseaux non concordants = NAT qui ne se déclenche jamais.

Task 4: Verify kernel forwarding is enabled

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Ce que ça signifie : Le noyau ne fera pas de forwarding entre interfaces. Les conteneurs peuvent parler à l’hôte, mais pas au‑delà.

Décision : Activez le forwarding de façon permanente via sysctl. Les basculements temporaires servent uniquement pour la réponse à incident.

cr0x@server:~$ sudo sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

Task 5: Check bridge netfilter sysctls (common in hardened builds)

cr0x@server:~$ sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables 2>/dev/null
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1

Ce que ça signifie : Le trafic ponté est passé à iptables/nftables pour filtrage. Cela peut être correct, mais cela signifie que vos politiques FORWARD comptent beaucoup.

Décision : Si vous n’aviez pas l’intention de filtrer le trafic intra‑bridge, envisagez de mettre ces valeurs à 0, ou écrivez des règles correctes. Ne devinez pas.

Task 6: See whether Docker created its expected chains (iptables view)

cr0x@server:~$ sudo iptables -S | sed -n '1,40p'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT

Ce que ça signifie : Docker a inséré des chaînes, mais votre politique FORWARD par défaut est DROP. Docker tente de percer des trous ; les gestionnaires de pare‑feu réinitialisent parfois les politiques et cassent cela.

Décision : Si le trafic échoue toujours, inspectez les compteurs et confirmez que ces sauts existent encore après les rechargements du pare‑feu. Sinon, le gestionnaire de pare‑feu écrase tout.

Task 7: Verify NAT MASQUERADE exists for container egress

cr0x@server:~$ sudo iptables -t nat -S | sed -n '1,80p'
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

Ce que ça signifie : MASQUERADE est présent. Si les conteneurs ne peuvent toujours pas atteindre Internet, soit le forwarding est bloqué, soit le DNS est cassé, soit les paquets utilisent une interface différente de celle que vous pensez.

Décision : Si cette ligne MASQUERADE manque, corrigez d’abord le conflit de backend. Réajouter la règle manuellement n’est qu’un pansement ; Docker vous combattra plus tard.

Task 8: Inspect the nftables ruleset directly (truth serum)

cr0x@server:~$ sudo nft list ruleset | sed -n '1,80p'
table inet filter {
	chain input {
		type filter hook input priority 0; policy accept;
	}
	chain forward {
		type filter hook forward priority 0; policy drop;
		jump DOCKER-USER
		jump DOCKER-ISOLATION-STAGE-1
	}
	chain output {
		type filter hook output priority 0; policy accept;
	}
	chain DOCKER-USER {
		return
	}
}
table ip nat {
	chain PREROUTING {
		type nat hook prerouting priority -100; policy accept;
	}
	chain POSTROUTING {
		type nat hook postrouting priority 100; policy accept;
		ip saddr 172.17.0.0/16 oifname != "docker0" masquerade
	}
}

Ce que ça signifie : Les règles pertinentes pour Docker existent dans nftables. Si iptables -S n’affiche rien mais que nft le fait, vous utilisez probablement iptables‑legacy pour l’inspection.

Décision : Standardisez sur un seul outil d’inspection et de gestion. En incident, la manière la plus rapide de perdre une heure est de regarder le mauvais jeu de règles.

Task 9: Prove whether firewall reload wipes Docker rules

cr0x@server:~$ sudo systemctl reload firewalld
cr0x@server:~$ sudo iptables -t nat -S | grep -E 'MASQUERADE|DOCKER' | head

Exemple de sortie (mauvais signe) :

cr0x@server:~$ sudo iptables -t nat -S | grep -E 'MASQUERADE|DOCKER' | head

Ce que ça signifie : Après le rechargement, les règles nat de Docker ont disparu (ou vous regardez le mauvais backend). Les conteneurs perdront l’accès sortant et les ports publiés.

Décision : Corrigez l’intégration : configurez la gestion de zone Docker dans firewalld, assurez‑vous que Docker démarre après le pare‑feu et réajoute les règles, ou cessez d’utiliser un gestionnaire qui remplace les jeux de règles sans précaution.

Task 10: Check DOCKER-USER chain for your policy insertion point

cr0x@server:~$ sudo iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -j RETURN

Ce que ça signifie : Docker a créé la chaîne mais vous ne l’utilisez pas. C’est correct, mais c’est aussi l’endroit où vous devriez placer vos règles « bloquer ce CIDR de conteneurs vers ce réseau ».

Décision : Placez la politique de l’entreprise dans DOCKER-USER, pas en modifiant la chaîne DOCKER de Docker. Docker considère ses propres chaînes comme un espace de travail temporaire.

Task 11: Confirm port publishing creates DNAT rules

cr0x@server:~$ docker run -d --name webtest -p 8080:80 nginx:alpine
Unable to find image 'nginx:alpine' locally
nginx:alpine: Pulling from library/nginx
Status: Downloaded newer image for nginx:alpine
3c1c0f4c8b2a3d0a5a1f2d7b3f4d9c2d12e4f0f3c2c7c9bb8a1a3c0f8ad9f1b2
cr0x@server:~$ sudo iptables -t nat -S DOCKER | grep 8080
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80

Ce que ça signifie : Docker a publié le port 8080 en créant une règle DNAT. Si l’application reste inatteignable, soit le filtrage FORWARD la bloque, soit le service n’écoute pas dans le conteneur.

Décision : Si aucun DNAT n’apparaît, Docker échoue à programmer iptables (souvent à cause d’un manque de capacités, de flags du démon ou d’un décalage de backend).

Task 12: Validate connectivity from inside a container (not from the host)

cr0x@server:~$ docker exec -it webtest sh -c 'ip route; wget -qO- --timeout=3 https://example.com | head'
default via 172.17.0.1 dev eth0
172.17.0.0/16 dev eth0 scope link  src 172.17.0.2

Ce que ça signifie : La route est correcte. Si wget se bloque ou échoue, suspectez le NAT/forwarding/pare‑feu ou le DNS. Si ça marche, votre problème est probablement la publication de ports entrants, pas l’accès sortant.

Décision : Séparez le problème : sortant vs entrant. Ils échouent pour des raisons différentes et nécessitent des règles différentes pour être réparés.

Task 13: Watch packet counters to see if rules match at all

cr0x@server:~$ sudo iptables -t nat -L POSTROUTING -v -n | sed -n '1,10p'
Chain POSTROUTING (policy ACCEPT 120 packets, 7200 bytes)
 pkts bytes target     prot opt in     out     source               destination
  38  2280 MASQUERADE  all  --  *      eth0    172.17.0.0/16        0.0.0.0/0

Ce que ça signifie : Des compteurs qui augmentent = la règle NAT matche. Si les compteurs sont à zéro alors que des conteneurs tentent l’accès sortant, le trafic peut ne pas être forwardé, peut sortir par une interface différente, ou être bloqué plus tôt.

Décision : Utilisez les compteurs pour éviter les changements de règles en mode cargo‑cult. Si les compteurs ne bougent pas, votre « correctif » n’est pas au bon endroit.

Task 14: Find who last touched netfilter rules (journal clues)

cr0x@server:~$ sudo journalctl -u docker -u firewalld -u ufw --since "2 hours ago" | tail -n 25
Jan 02 09:14:22 server dockerd[1289]: time="2026-01-02T09:14:22.902311" level=info msg="Loading containers: done."
Jan 02 09:14:23 server dockerd[1289]: time="2026-01-02T09:14:23.110022" level=info msg="Default bridge (docker0) is assigned with an IP address 172.17.0.1/16. Daemon option --bip can be used to set a preferred IP address"
Jan 02 09:41:07 server firewalld[1022]: Reloaded firewall rules
Jan 02 09:41:07 server dockerd[1289]: time="2026-01-02T09:41:07.514010" level=warning msg="Failed to program NAT chain: iptables: No chain/target/match by that name."

Ce que ça signifie : Un rechargement de firewalld a eu lieu, puis Docker a échoué à programmer des règles. C’est votre coupable évident : les tables de règles ont été remplacées et les références attendues par Docker ont cassé.

Décision : Corrigez l’ordonnancement des services, la configuration du gestionnaire de pare‑feu, ou réduisez la propriété des règles (voir section stratégie). Ne redémarrez pas Docker en boucle comme « solution ».

Choisir une stratégie : iptables legacy, nftables‑first, ou « Docker ne touche pas mon pare‑feu »

La plupart des pannes viennent d’une indécision. Les équipes exécutent accidentellement un hybride : Docker suppose iptables, la distribution suppose nftables, et le gestionnaire de pare‑feu suppose qu’il possède l’ensemble du jeu de règles. Choisissez un modèle et mettez‑le en œuvre délibérément.

Stratégie A : Standardiser sur iptables-legacy (la voie « ennuyeuse et prévisible »)

Si vous avez besoin d’une compatibilité maximale avec des outils anciens ou que vous démêlez des années de scripts, iptables‑legacy peut être la moins mauvaise option. Il réduit les bizarreries de traduction et s’aligne sur les hypothèses originales de Docker.

Quand le choisir :

  • Distributions plus anciennes ou combinaisons noyau/espace utilisateur où l’outillage nft est incohérent.
  • Beaucoup d’habitudes opérationnelles autour de la sortie iptables et des scripts.
  • Peu d’appétence pour migrer la politique du pare‑feu maintenant.

Coût : Vous pariez contre l’orientation de la distribution. Ça peut aller, mais assumez la décision.

Esquisse d’implémentation (Debian/Ubuntu) :

cr0x@server:~$ sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
update-alternatives: using /usr/sbin/iptables-legacy to provide /usr/sbin/iptables (iptables) in manual mode

Point de décision : Après le changement, redémarrez Docker et confirmez que les règles apparaissent avec iptables -S. Si elles n’apparaissent toujours pas, quelque chose d’autre cloche (permissions, flags du démon, gestionnaire de pare‑feu qui efface).

Stratégie B : Aller nftables‑first (la voie « noyau moderne, règles modernes »)

C’est la direction du noyau Linux. C’est aussi là où les gens se font mal quand ils migrent à moitié. Si vous choisissez nftables‑first, vous devez être discipliné sur la propriété des règles et l’inspection.

Ce que « nftables‑first » signifie réellement :

  • Utiliser iptables uniquement comme interface de compatibilité si nécessaire, mais inspecter avec nft.
  • S’assurer que votre gestionnaire de pare‑feu utilise nftables et ne supprime pas les chaînes gérées par Docker, ou qu’il s’intègre explicitement avec Docker.
  • Persister correctement les règles nftables et éviter les outils qui « flushent et remplacent tout » sans coordination.

Point opérationnel important : Docker peut toujours émettre des commandes iptables, mais si celles‑ci sont iptables‑nft, ça marche. L’important est de ne pas mélanger nft et legacy et d’espérer un comportement cohérent.

Stratégie C : Dire à Docker d’arrêter de gérer iptables (pour ceux qui contrôlent vraiment la bordure)

Docker a un flag de démon : --iptables=false. C’est tentant. C’est aussi une arme tranchante qui coupe en silence.

Quand c’est sensé :

  • Vous avez une équipe réseau dédiée qui gère tout le NAT/filtrage avec nftables.
  • Vous exécutez des réseaux de conteneurs fixes et les ports publiés sont définis explicitement dans les règles du pare‑feu.
  • Vous acceptez que Docker ne « marche pas tout seul » pour les développeurs.

Quand c’est un piège :

  • Vous comptez sur la publication de ports dynamique ou des stacks Compose éphémères.
  • Plusieurs équipes déploient des conteneurs arbitraires sur le même hôte.

Conseil avec opinion : Si vous n’êtes pas prêt à écrire et maintenir l’équivalent des règles NAT et de forwarding de Docker vous‑même, ne désactivez pas iptables de Docker. Le noyau ne récompensera pas votre optimisme.

Deuxième petite blague : Désactiver iptables de Docker, c’est comme enlever le volant parce que vous « préférez ressentir la route ». Vous la ressentirez certainement.

La seule chaîne à respecter : DOCKER-USER

Docker vous donne un emplacement supporté pour appliquer la politique : DOCKER-USER. Il est évalué avant les règles propres à Docker pour le trafic forwardé. Placez-y vos règles allow/deny. Gardez‑les petites. Lisibles. Auditable.

Exemple de politique : bloquer les conteneurs vers les réseaux RFC1918 sauf un sous‑réseau de service nécessaire. (Ajustez selon votre environnement.)

cr0x@server:~$ sudo iptables -I DOCKER-USER 1 -d 10.0.0.0/8 -j REJECT
cr0x@server:~$ sudo iptables -I DOCKER-USER 2 -d 172.16.0.0/12 -j REJECT
cr0x@server:~$ sudo iptables -I DOCKER-USER 3 -d 192.168.0.0/16 -j REJECT
cr0x@server:~$ sudo iptables -A DOCKER-USER -j RETURN

Ce que ça signifie : Les conteneurs peuvent toujours atteindre Internet (NAT), mais ne peuvent pas se promener latéralement dans votre réseau d’entreprise. Vous utilisez le crochet supporté par Docker au lieu de lutter contre ses chaînes.

Décision : Si vous avez besoin de politique, utilisez DOCKER-USER. Si vous avez besoin d’une politique complexe, envisagez de déplacer le réseau des conteneurs vers une solution avec politique réseau explicite (en acceptant la charge opérationnelle).

Une citation unique (idée paraphrasée) pour rester lucide

Richard Cook (idée paraphrasée) : « Le succès cache la complexité ; l’échec la révèle. »

Quand le réseau Docker fonctionne, personne ne demande qui possède le pare‑feu. Quand ça casse, vous découvrez exactement combien d’outils écrivaient des règles à votre insu.

Trois mini‑histoires d’entreprise issues du terrain

Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse

L’équipe avait une routine : déployer une nouvelle image, surveiller le health check, passer à autre chose. Un mardi, ils ont poussé un patch mineur vers un service de paiement qui publiait un port sur l’hôte. Le service est devenu sain, mais le trafic client s’effondre. Du point de vue de l’app, rien n’était cassé. Du point de vue du load balancer, le backend a cessé d’accepter les connexions.

Le premier intervenant a fait ce que fait tout ingénieur fatigué : vérifié docker ps, confirmé -p 443:8443 et regardé iptables. Aucune règle DNAT. Aucune référence à la chaîne DOCKER. « Docker n’a pas programmé iptables », a‑t‑il dit, et a redémarré Docker. Ça a tenu une minute. Puis c’est reparti après un rechargement du pare‑feu. Là l’incident est devenu intéressant.

La mauvaise hypothèse était subtile : ils supposaient que le système utilisait iptables legacy parce que iptables -S ne montrait rien d’utile. En réalité, l’hôte était passé à nftables des mois plus tôt lors d’une mise à jour OS. Leur runbook utilisait encore l’outillage iptables‑legacy, et leur étape de vérification inspectait le mauvais backend. Les règles existaient, juste pas là où ils regardaient.

Entre‑temps, un job de durcissement de sécurité s’exécutait toutes les 15 minutes et rechargeait la politique du pare‑feu en nftables, remplaçant l’ensemble du jeu de règles de façon atomique. Les règles iptables-backed nft de Docker n’étaient pas préservées parce que le job de durcissement ne les incluait pas. Toutes les quinze minutes, le port publié disparaissait. C’était un métronome parfait d’indisponibilité.

La correction fut ennuyeuse : cesser de remplacer l’ensemble du jeu de règles nft, intégrer les chaînes requises par Docker, et épingler l’outillage d’inspection. La leçon a marqué parce que ça a fait mal : un « simple redeploy » est devenu une leçon sur les backends de pare‑feu à volume de production.

Mini‑histoire 2 : L’optimisation qui a mal tourné

Une autre entreprise cherchait à optimiser le temps de démarrage. Quelqu’un a noté que Docker prenait parfois plus de temps à démarrer sur un hôte chargé. Ils ont profilé et vu du temps passé à manipuler les règles iptables. L’optimisation proposée : désactiver la gestion iptables de Docker et laisser le « vrai pare‑feu » s’en occuper. Démarrage plus rapide, moins de pièces mobiles, meilleure posture sécurité. Sur une présentation, ça paraissait la maturité.

Ils ont déployé le changement en staging où les apps avaient des ports stables et des réseaux fixes. Tout a passé. Puis en production, où les équipes utilisaient Compose et des services temporaires publiant des ports pour le debug. Ces ports ont commencé à échouer d’une manière agaçante : les conteneurs tournaient, loguaient « listening », et curl local depuis l’hôte fonctionnait via l’IP du conteneur. Mais les ports hôtes publiés étaient morts parce qu’aucune règle DNAT n’avait été créée. Le symptôme opérationnel était « des ports aléatoires ne fonctionnent pas », excellent déclencheur d’une semaine de pointage de doigts.

Pour patcher, ils ont ajouté des règles nft statiques pour « les services importants ». Ça a aidé jusqu’à la prochaine équipe qui déployait quelque chose de nouveau. Les règles du pare‑feu sont devenues un registre manuel de ports qui a dérivé de la réalité. L’optimisation de démarrage a créé une taxe d’on‑call : chaque nouveau service nécessitait une PR pour le pare‑feu. Chaque incident nécessitait quelqu’un qui comprenait nftables et le réseau Docker. Le gain de temps de démarrage a été remboursé avec intérêts composés.

Ils ont finalement réactivé la gestion iptables de Docker et déplacé la politique dans DOCKER-USER. Le temps de démarrage a légèrement régressé. Les incidents ont fortement diminué. Morale : les gains de performance qui suppriment l’automatisation déplacent souvent le travail vers les humains, et les humains sont notoirement non déterministes.

Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une troisième équipe travaillait dans un environnement réglementé. Ils traitaient l’état du pare‑feu comme du code : configuration versionnée, propriété explicite, et tests d’intégration qui validaient que les chaînes requises existaient après les rechargements. Pas d’héroïsme, juste de la discipline. C’est le genre de pratique qui semble lente jusqu’à ce qu’elle ne le soit plus.

Lors d’un patch OS de routine, la distribution a changé l’alternative iptables par défaut de legacy à nft. Au premier reboot, la moitié des conteneurs a perdu la connectivité sortante. Ça ressemblait à un typique « Docker cassé après patch », le genre d’événement qui ruine les weekends.

Mais leur monitoring avait une vérification ciblée : vérifier que le MASQUERADE pour le sous‑réseau des conteneurs existe et que les compteurs de paquets augmentent sous trafic synthétique. L’alerte a déclenché en quelques minutes après le reboot. L’on‑call a suivi un runbook qui commençait par « vérifier le backend » et « vérifier les compteurs NAT », pas « redémarrer Docker jusqu’à ce que ça marche ».

Ils ont trouvé le décalage rapidement : leur gestionnaire de pare‑feu chargeait des règles legacy alors que le binaire iptables cible désormais nftables. Deux univers parallèles. La correction fut tout aussi peu glamour : épingler explicitement les alternatives iptables dans la gestion de configuration, et ajouter une vérification post‑reload qui inspecte le jeu de règles actif avec nft list ruleset.

Rien de magique. Ils avaient juste moins d’hypothèses. Voilà à quoi ressemble le « ennuyeux » quand ça marche.

Erreurs courantes : symptôme → cause racine → correction

1) Les conteneurs n’ont pas Internet, mais le DNS résout

Symptôme : dig fonctionne ; curl se bloque ou time‑out dans les conteneurs.

Cause racine : Règle MASQUERADE manquante ou forwarding bloqué (politique FORWARD DROP, règles ACCEPT manquantes).

Correction : Confirmez le NAT dans le bon backend ; assurez‑vous que net.ipv4.ip_forward=1 ; assurez‑vous que les règles FORWARD autorisent l’e‑gress de docker0 et les retours RELATED,ESTABLISHED.

2) Les ports publiés ont cessé de fonctionner après un rechargement du pare‑feu

Symptôme : docker run -p fonctionne jusqu’à un reload de firewalld/ufw ; ensuite l’entrée casse.

Cause racine : Le gestionnaire de pare‑feu remplace le jeu de règles et supprime les chaînes/jumps DNAT de Docker.

Correction : Configurez le gestionnaire de pare‑feu pour préserver les chaînes Docker ou ordonnez les services pour que Docker reprogramme les règles après le reload. Préférez la politique dans DOCKER-USER.

3) iptables n’affiche pas de règles Docker, mais les conteneurs tournent

Symptôme : iptables -S semble vide ; vous supposez que Docker n’a pas mis de règles.

Cause racine : Vous regardez iptables‑legacy tandis que Docker écrit via iptables‑nft (ou vice versa).

Correction : Vérifiez iptables --version et les alternatives. Inspectez avec nft list ruleset si c’est nft‑backed.

4) Le trafic inter‑conteneurs sur le même bridge est bloqué

Symptôme : Les conteneurs atteignent Internet mais ne se joignent pas entre eux par IP.

Cause racine : Bridge netfilter activé avec des règles FORWARD restrictives, ou isolation Docker plus des règles personnalisées bloquant l’intra‑bridge.

Correction : Autorisez -i docker0 -o docker0 dans le chemin forward (ou l’équivalent nft). Re‑vérifiez les sysctls bridge-nf-call-iptables.

5) Docker échoue à démarrer avec « Failed to program NAT chain »

Symptôme : Les logs du démon Docker affichent des erreurs sur des chaînes/cibles manquantes.

Cause racine : Un autre outil a flushé les tables pendant la configuration de Docker, ou vous avez des modules/backends iptables incohérents.

Correction : Corrigez l’ordonnancement des services ; arrêtez de flush les tables derrière Docker ; alignez le backend iptables ; redémarrez Docker après avoir assuré un état stable du pare‑feu.

6) Seuls quelques hôtes d’un parc ont le problème

Symptôme : « Ça marche sur l’hôte A, cassé sur l’hôte B » après le même déploiement.

Cause racine : Alternatives iptables différentes, gestionnaires de pare‑feu différents, ou sysctls noyau différents issus de baselines de durcissement.

Correction : Standardisez : épinglez les alternatives, imposez les sysctls, et validez avec des vérifications automatisées qui confirment l’existence des règles NAT/forwarding dans le backend actif.

7) IPv6 fonctionne mais IPv4 non (ou l’inverse)

Symptôme : Les conteneurs atteignent des destinations IPv6 mais pas IPv4, ou l’inverse.

Cause racine : Chemins de règles séparés : ip6tables vs iptables (ou tables inet en nft). Un côté a été configuré, l’autre pas.

Correction : Inspectez les deux familles. En nft, préférez table inet pour les règles filter quand c’est approprié, et assurez‑vous que le NAT existe pour la famille d’adresses qui vous importe.

Listes de contrôle / plan pas à pas

Checklist A: Stabiliser un hôte cassé pendant un incident (15 minutes, sans héroïsme)

  1. Confirmer le backend : iptables --version, puis vérifier avec nft list ruleset si c’est nft‑backed.
  2. Confirmer le forwarding : sysctl net.ipv4.ip_forward et mettre à 1 si nécessaire.
  3. Confirmer le NAT : rechercher le MASQUERADE pour le sous‑réseau Docker dans le backend actif.
  4. Confirmer le chemin FORWARD : s’assurer que DOCKER-USER et les règles ACCEPT de Docker existent et sont référencées.
  5. Vérifier si un rechargement du pare‑feu efface les règles. Si oui, arrêter l’hémorragie : éviter temporairement les reloads, ou redémarrer Docker après un reload comme solution de court terme.
  6. Utiliser les compteurs pour confirmer que les paquets atteignent les règles NAT et forward.

Checklist B: Rendre la correction permanente (la partie que tout le monde saute puis regrette)

  1. Choisir le propriétaire de la politique : soit Docker gère les règles iptables (par défaut), soit votre pare‑feu le fait et Docker est contraint. Ne faites pas « les deux ».
  2. Épingler le backend iptables de façon cohérente : utilisez les alternatives pour assurer l’uniformité sur le parc.
  3. Verrouiller l’ordre des services : Docker doit démarrer après que le pare‑feu soit prêt, et les reloads du pare‑feu ne doivent pas effacer les chaînes Docker.
  4. Mettre la politique personnalisée dans DOCKER-USER : c’est le crochet supporté et stable.
  5. Persister les sysctls : forwarding et sysctls bridge doivent être définis via /etc/sysctl.d/.
  6. Ajouter des validations : vérifications post‑boot et post‑reload qui valident la présence de MASQUERADE et des sauts de chaîne dans le backend actif.

Checklist C: Migration propre de iptables legacy vers nftables (sans downtime surprise)

  1. Inventorier les règles iptables actuelles et identifier lesquelles sont gérées par Docker vs la politique du site.
  2. Déplacer la politique du site dans DOCKER-USER quand c’est possible.
  3. Changer les runbooks d’inspection d’iptables vers nft (ou assurer l’utilisation cohérente d’iptables‑nft).
  4. Tester le comportement de rechargement du pare‑feu en staging : confirmer que les chaînes Docker persistent et que les ports publiés survivent aux reloads.
  5. Déployer sur un petit canari d’hôtes ; valider avec des contrôles synthétiques d’egress/entrée conteneur.
  6. Ce n’est qu’alors qu’il faut changer l’ensemble du parc.

FAQ

1) Pourquoi Docker utilise‑t‑il encore iptables en 2026 ?

Parce que l’interface noyau pour le filtrage/NAT est netfilter, et iptables a été l’interface pratique pendant des années. Le modèle de Docker repose sur l’insertion incrémentale de chaînes et de règles. nftables est plus récent et meilleur à bien des égards, mais « plus récent » ne signifie pas automatiquement « tous les composants de l’écosystème ont migré ».

2) Quelle est la différence entre iptables-nft et nftables ?

iptables-nft est la commande iptables qui écrit des règles dans nftables en utilisant une couche de traduction. nftables natif utilise nft et son propre langage et objets. Le détail opérationnel important : les outils qui gèrent nftables nativement peuvent remplacer les jeux de règles de façon atomique et supprimer accidentellement les règles créées via iptables‑nft sauf s’ils les préservent intentionnellement.

3) Comment savoir si je regarde les « bonnes » règles ?

Commencez par iptables --version. Si ça indique (nf_tables), alors nft list ruleset est la vue faisant autorité. Si vous voyez (legacy), alors la sortie iptables reflète les règles actives. Ne vous fiez pas à la mémoire musculaire ; vérifiez.

4) Dois‑je utiliser firewalld avec Docker ?

Vous pouvez, mais il faut le configurer intentionnellement. Le principal mode de défaillance est que le rechargement de firewalld efface les règles de Docker ou change le comportement de forwarding par défaut. Si vous utilisez firewalld, testez le comportement de reload, confirmez que les chaînes Docker persistent, et évitez les politiques qui flushent/remplacent l’ensemble du jeu de règles sans prendre Docker en compte.

5) Où dois‑je placer les règles « bloquer les conteneurs vers X » ?

Utilisez la chaîne DOCKER-USER. Docker l’attend et ne l’écrasera pas. N’éditez pas les chaînes propres à Docker comme magasin de politique à long terme.

6) Est‑ce sûr de redémarrer Docker pour « réparer le réseau » ?

C’est une action tactique, pas une correction. Redémarrer Docker peut reprogrammer temporairement les règles, mais si un gestionnaire de pare‑feu recharge et les efface à nouveau, vous reviendrez au même problème — avec plus de perturbation. Utilisez les redémarrages pour rétablir le service, puis corrigez la propriété et l’ordre des services.

7) Qu’en est‑il de Docker rootless ?

Rootless change le modèle réseau ; il utilise souvent un réseau en espace utilisateur (comme slirp4netns) plutôt que de programmer iptables de l’hôte. Cela peut éviter la guerre des pare‑feu, mais comporte d’autres compromis de performance et de fonctionnalités, en particulier pour la publication de ports et les exigences réseau bas‑niveau.

8) Pourquoi la politique FORWARD à DROP importe si INPUT est ACCEPT ?

Le trafic des conteneurs traversant l’hôte frappe la chaîne FORWARD, pas INPUT. INPUT concerne le trafic destiné à l’hôte lui‑même. Si FORWARD est DROP et que vous n’avez pas les exceptions appropriées, les conteneurs seront piégés sur leur bridge comme dans une prison très polie.

9) Puis‑je utiliser nftables pour la sécurité de l’hôte et laisser Docker gérer le NAT ?

Oui, si vous le faites de manière cohérente. Laissez Docker programmer des règles via iptables‑nft dans nftables, et assurez‑vous que votre gestion de nftables ne wipe pas ces règles. Mettez votre politique personnalisée dans DOCKER-USER (via iptables) ou créez des règles natives nft qui s’intègrent sans flush complet des tables Docker.

10) Ma mise à jour de distro a basculé le backend et maintenant tout est bizarre. Quelle est la réponse la plus sûre ?

Choisissez un backend et appliquez‑le sur tout le parc. Pendant la stabilisation, beaucoup d’équipes choisissent iptables‑legacy parce que cela correspond aux hypothèses antérieures. À plus long terme, migrer vers nftables‑first est raisonnable — mais faites‑le avec des tests couvrant les reloads, le NAT, le forwarding et les ports publiés.

Conclusion : les prochaines étapes qui tiennent dans le temps

Les pannes réseau Docker autour d’iptables/nftables ne sont pas mystérieuses. Ce sont des problèmes politiques. Trop d’acteurs, pas assez de propriété. Votre travail consiste à mettre fin à la guerre en faisant un choix clair et en l’appliquant.

  1. Décidez du backend (nftables‑first ou iptables‑legacy) et épinglez‑le via alternatives/gestion de configuration.
  2. Vérifiez forwarding et NAT avec des compteurs, pas au doigt mouillé.
  3. Prouvez la persistance des règles après les rechargements du pare‑feu et les reboots. Si les règles disparaissent, corrigez l’ordre ou cessez de remplacer l’ensemble du jeu de règles.
  4. Utilisez DOCKER-USER comme crochet de politique stable. Laissez les chaînes Docker à Docker.
  5. Automatisez une vérification post‑boot qui valide : le backend, la présence de MASQUERADE, le saut FORWARD vers DOCKER-USER, et qu’au moins un conteneur synthétique atteint un endpoint externe.

Si vous appliquez ces cinq points, vous n’éliminerez pas toutes les incidents réseau — c’est Linux, pas un conte de fées — mais vous éliminerez la classe d’incidents où vous déboguez le mauvais univers de pare‑feu.

← Précédent
Proxmox « Trop de fichiers ouverts » : quelle limite augmenter, et pour quel service
Suivant →
MariaDB vs PostgreSQL : limitation de débit via la BDD — pourquoi c’est un piège et quoi faire à la place

Laisser un commentaire