Vous lancez docker run -p 8080:80 ... ou montez une stack Compose, et Docker répond par l’équivalent opérationnel d’un « non » :
bind: address already in use. Soudain vous faites de l’intervention d’incident un mardi parce qu’un port est occupé et personne
ne se souvient pourquoi.
C’est l’une de ces erreurs qui semblent simples tant qu’elles ne le sont pas. Parfois c’est Nginx. Parfois c’est un conteneur zombie, une socket systemd,
une limitation du Docker rootless, ou le fantôme d’un processus mort qui a laissé un écouteur. Trouvons le véritable propriétaire, choisissons la solution la moins mauvaise,
et laissons l’hôte plus propre que nous l’avons trouvé.
Ce que l’erreur signifie réellement (et ce qu’elle ne signifie pas)
Quand Docker « publie » un port (par exemple -p 8080:80), il demande au noyau de l’hôte un socket d’écoute sur le port hôte
(ici 8080). Le noyau répond soit « d’accord », soit « non ». Le « non » que vous voyez est typiquement EADDRINUSE : un autre socket possède déjà cette
4‑tupla exacte (protocole, adresse locale, port local).
L’astuce est que « déjà utilisé » est plus large que « une autre app tourne ». Cela peut être :
- Un autre processus écoutant sur ce port (
nginx,apache,node,sshd, etc.). - Un autre conteneur ayant déjà publié ce port hôte.
- Un service système qui se lie tôt via l’activation de socket
systemd. - Le propre helper de Docker (historiquement
docker-proxy) maintenant le port, même si vous pensez que le conteneur a disparu. - Un bind sur une IP spécifique, empêchant une tentative ultérieure de binder
0.0.0.0(ou vice versa). - Limitations de Docker rootless : binder des ports faibles nécessite des étapes supplémentaires.
Ce que ce n’est pas : un problème de pare-feu. Les pare-feu bloquent le trafic ; ils n’empêchent pas la création de sockets d’écoute. Si vous voyez « address already in use »,
vous cherchez presque toujours un écouteur, un proxy laissé en place, ou une collision de configuration.
Un bon modèle mental : la publication d’un port est une revendication. Docker veut revendiquer le port sur l’hôte. Le noyau dit que quelqu’un d’autre a déjà le titre.
Il faut trouver qui le possède et décider d’extraire ce propriétaire, de négocier une nouvelle adresse, ou de déplacer votre service.
Playbook de diagnostic rapide
Vous êtes sous pression temporelle. Très bien. Faites les vérifications minimales dans le bon ordre. Ne commencez pas à « redémarrer Docker » comme si c’était une exorcisme.
Première étape : confirmer le port exact, le protocole et l’adresse de bind
- Regardez la ligne d’erreur Docker. Est-ce
0.0.0.0:80,127.0.0.1:8080, ou une IP d’interface spécifique ? - TCP vs UDP compte. Deux protocoles différents peuvent utiliser le même numéro de port.
Deuxième étape : identifier le vrai écouteur
ss -ltnppour TCP,ss -lunppour UDP. Cela résout généralement le mystère.- Si vous voyez
docker-proxy, remontez jusqu’au conteneur qui l’a lancé. - Si vous voyez
systemdou une unité « .socket », vous êtes en présence d’activation de socket.
Troisième étape : décider de la correction propre
- Si c’est un vieux conteneur : stoppez/supprimez le conteneur, puis relancez avec votre mappage de port prévu.
- Si c’est un service hôte : déplacez le service hôte, publiez Docker sur un autre port, ou mettez un reverse proxy approprié devant.
- Si c’est l’activation de socket
systemd: stoppez/désactivez l’unité.socket, pas seulement l’unité de service. - Si c’est rootless et ports faibles : utilisez
setcap, des approches de type authbind, ou publiez des ports élevés et frontaux avec un proxy.
Quatrième étape : vérifier depuis l’extérieur
curllocalement, puis depuis un autre hôte si possible.- Confirmez que vous n’avez pas « réglé » le bind en cassant le routage (NAT, iptables/nftables) ou l’exposition IPv6.
Une petite blague courte, parce que vous l’avez mérité : Les ports sont comme des salles de réunion — tout le monde en a besoin en urgence, personne ne l’a réservé, et d’une manière ou d’une autre la compta est déjà à l’intérieur.
Faits et contexte historique (pour cesser de discuter avec le noyau)
Ce type de chose semble arbitraire tant que vous ne connaissez pas un peu l’histoire et le comportement du noyau. Voici des faits concrets qui comptent pour le débogage :
- Les ports sont possédés par protocole. TCP 443 et UDP 443 sont des revendications séparées. Un port TCP occupé ne bloque pas l’UDP du même numéro.
- L’adresse de bind compte. Un processus peut se binder sur
127.0.0.1:8080sans bloquer un autre processus qui se binderait sur192.168.1.10:8080— mais un bind sur0.0.0.0:8080bloque toutes les adresses IPv4. - Linux utilise
SO_REUSEADDRetSO_REUSEPORTdifféremment. Ce n’est pas « ignorer les conflits » ; ces options contrôlent la réutilisation d’adresse et la sémantique de répartition de charge. TIME_WAITn’est pas un socket d’écoute. C’est un état de connexion ; en général il n’empêche pas un nouvel écouteur sur ce port.- Docker utilisait historiquement un proxy en espace utilisateur (
docker-proxy) pour les ports publiés. Selon la version et la configuration, vous pouvez encore le voir, et il peut maintenir des ports même quand le réseau devient étrange. - La transition iptables → nftables a changé des modes de défaillance. Les distributions modernes exécutent nftables en dessous ; mélanger les outils peut rendre la portée des ports confuse, bien que cela ne provoque pas
EADDRINUSEen soi. - systemd socket activation peut binder avant que votre service ne démarre. L’écouteur appartient à l’unité socket, pas au démon que vous pensez avoir arrêté.
- Docker rootless ne peut pas binder les ports privilégiés par défaut. Le noyau applique les privilèges des ports bas ; il existe des contournements mais ils doivent être délibérés.
- IPv6 ajoute un comportement de bind parallèle. Un service peut binder
[::]:80et couvrir aussi IPv4 via des adresses mappées IPv6 selonnet.ipv6.bindv6only.
Une idée paraphrasée en fiabilité (parce que je ne prétendrai pas citer parfaitement) : l’espoir n’est pas une stratégie
— attribué dans les cercles opérations.
Le point est : prouvez qui possède le port avant de changer quoi que ce soit.
Tâches pratiques : commandes, sorties et décisions
Ce sont des tâches pratiques que vous pouvez lancer sur un hôte Linux. Chacune inclut : la commande, un extrait de sortie plausible, ce que cela signifie, et la décision à prendre.
Faites-les dans l’ordre quand vous êtes incertain ; piochez quand vous êtes confiant.
Task 1: Reproduce the exact bind target Docker is trying to claim
cr0x@server:~$ docker run --rm -p 8080:80 nginx:alpine
docker: Error response from daemon: driver failed programming external connectivity on endpoint inspiring_morse (8f4c9b5b1b6a): Bind for 0.0.0.0:8080 failed: port is already allocated.
Signification : Docker a tenté de binder le port TCP 8080 sur toutes les interfaces IPv4 (0.0.0.0) et a échoué parce que le port est déjà possédé.
Décision : Arrêtez de deviner et trouvez le propriétaire actuel de TCP/8080.
Task 2: Identify the listening process with ss (fast, modern)
cr0x@server:~$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=2147,fd=23))
Signification : PID 2147 (node) possède l’écoute sur TCP/8080 sur toutes les interfaces IPv4.
Décision : Décidez si ce service Node doit être déplacé, arrêté, ou mis derrière un reverse proxy pendant que Docker utilise un autre port.
Task 3: Show the full listener table to catch “wrong port” assumptions
cr0x@server:~$ sudo ss -ltnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=892,fd=3))
LISTEN 0 4096 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=1011,fd=6))
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=2147,fd=23))
LISTEN 0 4096 [::]:80 [::]:* users:(("nginx",pid=1320,fd=7))
Signification : Vous avez un mélange d’écouteurs en loopback et publics ; remarquez aussi [::]:80 en IPv6.
Décision : Si Docker tente de publier 80, vérifiez aussi les écouteurs IPv6. Si c’est 8080, le service Node est le conflit.
Task 4: Use lsof when you need filesystem and command-line context
cr0x@server:~$ sudo lsof -nP -iTCP:8080 -sTCP:LISTEN
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 2147 app 23u IPv4 56123 0t0 TCP *:8080 (LISTEN)
Signification : Confirme l’écouteur et le compte utilisateur, utile pour déterminer comment il est lancé (unité systemd ? cron ? PM2 ?).
Décision : Si c’est un « service mystère », vous avez maintenant un PID et un utilisateur à tracer.
Task 5: Trace the PID to a systemd unit (the “who started this?” question)
cr0x@server:~$ ps -p 2147 -o pid,ppid,user,cmd
PID PPID USER CMD
2147 1 app node /opt/api/server.js
cr0x@server:~$ systemctl status 2147
● api.service - Internal API
Loaded: loaded (/etc/systemd/system/api.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2026-01-01 09:13:22 UTC; 1d 2h ago
Main PID: 2147 (node)
Signification : L’écouteur est un service géré, pas un terminal aléatoire.
Décision : Coordonnez le changement : soit mettez à jour api.service pour déplacer les ports, soit publiez Docker sur un autre port.
Task 6: Check if Docker itself already published that port (container conflict)
cr0x@server:~$ docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'
NAMES IMAGE PORTS
web nginx:alpine 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
postgres postgres:16 127.0.0.1:5432->5432/tcp
Signification : Un autre conteneur possède déjà le port hôte 8080 et le mappe sur le port 80 du conteneur.
Décision : Ne démarrez pas un second service sur le même port hôte. Choisissez un nouveau port hôte, ou arrêtez/remplacez le conteneur existant.
Task 7: Find which compose project owns the port (when containers have polite names)
cr0x@server:~$ docker inspect -f '{{.Name}} {{range $p, $conf := .NetworkSettings.Ports}}{{$p}}={{(index $conf 0).HostIp}}:{{(index $conf 0).HostPort}} {{end}}' web
/web 80/tcp=0.0.0.0:8080
Signification : Confirme exactement quel conteneur et quel mapping a revendiqué le port hôte.
Décision : Si vous visiez un remplacement, faites un swap contrôlé (blue/green ou stop-then-start) au lieu de lutter contre le noyau.
Task 8: If docker-proxy is listening, map it back to a container
cr0x@server:~$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=3012,fd=4))
cr0x@server:~$ ps -p 3012 -o pid,cmd
PID CMD
3012 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 172.17.0.3 -container-port 80
Signification : Le proxy de Docker tient le port hôte et le transmet à une IP de conteneur. Le conteneur peut exister ou non.
Décision : Vérifiez l’état Docker ensuite ; si le conteneur est parti mais que le proxy reste, traitez cela comme un bug Docker/réseau et nettoyez prudemment.
Task 9: Correlate container IPs to a container (when you only have 172.17.0.3)
cr0x@server:~$ docker network inspect bridge -f '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
web 172.17.0.3/16
postgres 172.17.0.4/16
Signification : Le proxy envoie vers web.
Décision : Si vous essayez de démarrer un autre « web », vous avez besoin d’un plan de déploiement intentionnel ou d’un port hôte différent.
Task 10: If it’s systemd socket activation, stop the socket unit (not just the service)
cr0x@server:~$ sudo ss -ltnp 'sport = :80'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:80 0.0.0.0:* users:(("systemd",pid=1,fd=49))
cr0x@server:~$ systemctl list-sockets | grep -E '(:80|http)'
web.socket 0 4096 0.0.0.0:80 0.0.0.0:* LISTENING
cr0x@server:~$ sudo systemctl stop web.socket
cr0x@server:~$ sudo systemctl disable web.socket
Removed "/etc/systemd/system/sockets.target.wants/web.socket".
Signification : PID 1 (systemd) possède le port 80 via une unité socket. Arrêter le service ne libérera pas le port ; arrêter la socket le fera.
Décision : Désactivez la socket si Docker a besoin du port. Si la socket est nécessaire pour un démon hôte, ne la combattez pas — publiez Docker ailleurs.
Task 11: Confirm IPv6 is part of the conflict
cr0x@server:~$ sudo ss -ltnp | grep ':80 '
LISTEN 0 4096 [::]:80 [::]:* users:(("nginx",pid=1320,fd=7))
Signification : Quelque chose écoute sur le port IPv6 80. Selon les sysctls et l’application, cela peut aussi couvrir IPv4.
Décision : Si Docker tente de binder 0.0.0.0:80 et échoue, arrêtez/relocalisez l’écouteur IPv6 ou liez explicitement les services à des adresses distinctes.
Task 12: Use curl to confirm what’s currently serving the port
cr0x@server:~$ curl -sS -D- http://127.0.0.1:8080/ -o /dev/null
HTTP/1.1 200 OK
Server: Express
Content-Type: text/html; charset=utf-8
Connection: keep-alive
Signification : Le port 8080 n’est pas « mystérieusement alloué » ; il sert activement une application Express.
Décision : Vous ne libérerez pas ce port sans impacter quelqu’un. Planifiez un déplacement contrôlé ou un reverse proxy.
Task 13: When you suspect a stale container, list stopped containers and remove the right one
cr0x@server:~$ docker ps -a --filter "status=exited" --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
NAMES STATUS PORTS
web_old Exited (137) 2 hours ago 0.0.0.0:8080->80/tcp
cr0x@server:~$ docker rm web_old
web_old
Signification : Même les conteneurs arrêtés peuvent laisser un état confus chez les humains. Le mappage de port devrait disparaître une fois le conteneur supprimé,
mais vérifiez avec ss.
Décision : Si le port est toujours détenu après suppression, vous regardez un proxy ou un problème de démon — continuez à creuser, ne redémarrez pas aléatoirement.
Task 14: Verify Docker isn’t stuck with an orphaned proxy (rare, but real)
cr0x@server:~$ docker ps --filter "publish=8080"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
cr0x@server:~$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("docker-proxy",pid=3012,fd=4))
Signification : Aucun conteneur en cours ne revendique 8080, mais docker-proxy le fait toujours. C’est un état incohérent.
Décision : Préférez une correction ciblée : redémarrez Docker pour réconcilier les forwards de ports, mais faites-le intentionnellement (drain du trafic, avertir les parties prenantes, connaître le rayon d’impact).
Task 15: Restart Docker the safe way (when you must)
cr0x@server:~$ sudo systemctl status docker --no-pager
● docker.service - Docker Application Container Engine
Active: active (running) since Thu 2026-01-01 08:11:09 UTC; 1d 3h ago
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ sudo ss -ltnp 'sport = :8080'
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
Signification : L’écouteur a disparu, ce qui implique que le proxy était orphelin et qu’un redémarrage du démon l’a nettoyé.
Décision : Redéployez immédiatement le conteneur avec le mappage de port prévu, puis validez les chemins de trafic. Aussi : ouvrez un ticket pour comprendre pourquoi Docker a dérivé.
Task 16: Diagnose rootless Docker bind failures (looks similar, different cause)
cr0x@server:~$ docker context show
rootless
cr0x@server:~$ docker run --rm -p 80:8080 nginx:alpine
docker: Error response from daemon: driver failed programming external connectivity on endpoint hopeful_bardeen: Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: permission denied.
Signification : Celui-ci n’est pas « déjà utilisé » ; c’est « permission denied », mais les opérateurs le lisent souvent comme un problème de port.
Le mode rootless ne peut pas binder les ports privilégiés (<1024) sans aide.
Décision : Publiez un port élevé (par ex. 8080) et frontalez-le avec un proxy privilégié, ou configurez une solution basée sur les capacités de manière délibérée.
Deuxième petite blague, puis retour au travail : Redémarrer Docker pour résoudre un conflit de ports, c’est comme redémarrer le bureau pour retrouver votre agrafeuse. Ça marche, mais vous vous ferez des ennemis.
Choisir la correction propre : un arbre de décision
« Corriger proprement » n’est pas « kill -9 et on passe à autre chose ». Une correction propre est une action où vous comprenez la propriété, l’impact et la récurrence. Utilisez cet arbre de décision.
1) Le port est-il détenu par un service hôte dont vous avez réellement besoin ?
- Oui, et il doit rester sur ce port : Ne publiez pas Docker sur le même port. Publiez sur un port hôte différent et utilisez un reverse proxy (ou load balancer) pour router.
- Oui, mais il peut être déplacé : Changez la configuration du service hôte et déployez pendant une fenêtre contrôlée. Mettez à jour la surveillance et les règles de pare-feu.
- Non, c’est inattendu : Remontez qui l’a démarré (PID → parent → fichier d’unité ou conteneur) et supprimez-le proprement.
2) Le port est-il détenu par un autre conteneur ?
- C’est le même service (remplacement) : Faites un échange contrôlé. Avec Compose, cela peut signifier arrêter l’ancien service avant de démarrer le nouveau, ou utiliser des ports publiés différents pour blue/green.
- C’est un service différent : Négociez les ports. Ne créez pas un système non documenté « celui qui démarre en premier gagne ».
3) Le propriétaire est-il systemd via une unité socket ?
- Oui : Arrêtez/désactivez l’unité
.socket, ou changez-la pour écouter sur une adresse/port différent. Arrêter l’unité.serviceseule est de la théâtralité.
4) Est-ce un artefact réseau Docker obsolète ?
- Probable : Validez la vue de Docker (
docker ps,docker network inspect) par rapport à la vue du noyau (ss). S’ils divergent, redémarrez Docker de façon contrôlée.
5) Avez-vous réellement besoin de binder sur 0.0.0.0 ?
Publier sur toutes les interfaces est pratique et souvent une erreur. Si un service est destiné uniquement à un accès local (métriques, UI d’administration),
lienez-le à 127.0.0.1 ou à une interface de gestion spécifique et laissez-le hors d’Internet public.
Trois mini-histoires d’entreprise (douleur, apprentissage, paperasse)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une équipe a hérité d’un « simple » hôte : une VM, Docker installé, quelques conteneurs, et un reverse proxy. Le runbook indiquait que l’application vivait sur le port 8080.
L’astreinte a donc supposé que 8080 était le port de l’app. Raisonnable. Faux.
Lors d’une mise à jour routinière, ils ont déployé un nouveau conteneur avec -p 8080:80 et ont rencontré « port already allocated ». Ils ont fait ce que font les gens quand le temps presse :
ils ont choisi un autre port (8081), mis à jour le reverse proxy, et ont appelé ça réglé. Le trafic est revenu.
Deux heures plus tard, une autre alerte a sonné : des callbacks internes échouaient. Un système en amont avait une dépendance codée en dur sur http://host:8080.
Cette dépendance existait parce que, des mois plus tôt, quelqu’un avait contourné le reverse proxy « temporairement », et « temporaire » était devenu de l’infra.
La mauvaise hypothèse ne portait pas sur Docker. Elle portait sur la propriété et les contrats d’interface. Le port 8080 n’était pas « l’app ». C’était « le contrat sur lequel d’autres équipes ont codé ».
Une fois cela compris, la correction fut ennuyeuse : remettre le reverse proxy sur 8080, déplacer le service interne sur un port non conflictuel, et documenter le contrat.
L’action de suivi fut la vraie victoire : ils ajoutèrent une vérification d’inventaire des écouteurs aux déploiements. Si la propriété d’un port change, le déploiement échoue bruyamment avant la production.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre organisation a décidé « d’optimiser » le réseau hôte. Ils ont voulu moins de pièces mobiles, donc ont basculé certains services sensibles à la latence sur --network host.
Cela élimine la NAT et peut réduire la surcharge. Cela supprime aussi les garde-fous.
Le premier mois s’est bien passé. Puis un développeur a livré un sidecar avec un port par défaut 9090, lui aussi en host networking.
Sur un nœud, 9090 était déjà utilisé par un exportateur de métriques. Sur un autre, non. Le rollout a donc réussi « parfois ».
Le mode de défaillance était désagréable : le conteneur démarrait sur les nœuds où le port était libre, et échouait ailleurs avec « bind: address already in use ».
Ou pire, le sidecar démarrait en premier et volait le port, et l’exportateur échouait. Même port, deux services, ordre de démarrage non déterministe.
L’« optimisation » n’était pas intrinsèquement mauvaise, mais exigeait une discipline qui leur manquait : allocation explicite des ports, validation au niveau du nœud, et une politique contre les ports par défaut.
Ils firent rollback du host networking pour la plupart des services et le conservèrent uniquement là où c’était justifié — et documentèrent les ports autorisés par nœud.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une plateforme proche des paiements gérait plusieurs hôtes Docker avec une gestion stricte des changements. Rien d’excitant. Ils avaient aussi une habitude simple :
chaque hôte exécutait un job nocturne qui capturait un instantané des sockets d’écoute et les mapait aux noms d’unités et aux conteneurs.
Un matin, un hôte a commencé à rejeter un déploiement avec la familière erreur de bind. L’astreinte a tiré l’instantané de la veille et l’a comparé à « maintenant ».
Le port 443 avait un nouvel écouteur détenu par une unité socket systemd qui ne faisait pas partie de la baseline. Cela a réduit la recherche de « n’importe quoi sur l’hôte » à « changements système récents ».
Le coupable fut une mise à jour de paquet qui a activé une unité socket par défaut pour une UI web fournie. Le service lui‑même n’était même pas encore en cours — systemd tenait le port préventivement.
Sans l’instantané, ils auraient cherché Docker networking et iptables pendant une heure.
La correction fut triviale : désactiver l’unité socket inattendue, relancer le déploiement, et mettre un pin sur le paquet jusqu’à ce qu’ils puissent re-loger l’UI.
La pratique était ennuyeuse et efficace : savoir ce qui écoute, et savoir quand ça change.
Erreurs courantes : symptômes → cause racine → correction
Voici les schémas qui reviennent en production. Les symptômes ont souvent l’air identiques. Les corrections, non.
1) Symptom: Docker says “port is already allocated” but you can’t find a process
- Cause racine : L’écouteur est en IPv6 uniquement (
[::]:PORT) ou lié à une IP spécifique que vous n’avez pas vérifiée, ou il est tenu pardocker-proxy. - Correction : Utilisez
ss -ltnpsans filtre d’abord, puis vérifiez IPv4 et IPv6. Si c’estdocker-proxy, remontez au conteneur via sa ligne de commande oudocker network inspect.
2) Symptom: You stopped the service but the port is still busy
- Cause racine : Activation de socket systemd. L’unité
.socketpossède le port, pas le processus que vous avez arrêté. - Correction : Arrêtez/désactivez l’unité socket :
systemctl stop something.socketetsystemctl disable something.socket.
3) Symptom: Compose deploy works on one host, fails on another
- Cause racine : Consommateurs de ports cachés diffèrent entre hôtes (exportateurs métriques, services dev locaux, defaults de distro).
- Correction : Standardisez les écouteurs de baseline et vérifiez-les en CI/CD preflight. Ou cessez de publier des ports fixes par hôte et placez les services derrière un reverse proxy.
4) Symptom: You “fixed” the bind by changing to a random high port, then downstream systems fail
- Cause racine : Le numéro de port faisait partie d’un contrat (clients codés en dur, règles de pare-feu, allowlists, contrôles de monitoring).
- Correction : Restaurez le port du contrat et déplacez le service en conflit. Si vous devez changer le contrat, coordonnez et versionnez-le comme une API.
5) Symptom: Docker publishes on IPv4, but service is only reachable on localhost (or vice versa)
- Cause racine : Mismatch de binding : l’hôte bind sur
127.0.0.1alors que les clients attendent un accès externe, ou le service à l’intérieur du conteneur n’écoute que127.0.0.1au lieu de0.0.0.0. - Correction : Alignez les couches de binding. Pour les conteneurs, assurez-vous que le process écoute sur
0.0.0.0à l’intérieur. Pour la publication hôte, utilisez la bonne IP hôte dans-p(ex.-p 127.0.0.1:8080:80).
6) Symptom: After removing a container, the port stays allocated
- Cause racine :
docker-proxyorphelin ou état du démon bloqué. - Correction : Confirmez qu’aucun conteneur ne publie le port. Si aucun, redémarrez Docker de manière contrôlée et revérifiez les écouteurs. Si ça persiste, investiguez les processus restants et les logs Docker.
7) Symptom: “bind: permission denied” while trying to publish port 80 in rootless Docker
- Cause racine : Restriction de port privilégié en mode rootless.
- Correction : Publiez un port élevé et frontalez-le avec un reverse proxy privilégié, ou implémentez une approche basée sur les capacités de façon délibérée (et documentez la sécurité).
8) Symptom: You can’t bind to 0.0.0.0:PORT, but binding to 127.0.0.1:PORT works
- Cause racine : Quelque chose possède déjà le wildcard bind (
0.0.0.0) sur ce port, ou votre service essaye de réclamer le wildcard alors qu’un autre service est lié à une adresse spécifique avec des flags exclusifs. - Correction : Inspectez les écouteurs par adresse. Décidez si vous voulez le port sur toutes les interfaces ; liez explicitement à l’IP requise, ou déplacez le service en conflit.
Listes de contrôle / plan pas à pas
Checklist A: When you just need Docker to start (without making it someone else’s problem)
- Lisez la ligne d’erreur. Notez le protocole, l’adresse et le port (
0.0.0.0:PORTvs127.0.0.1:PORT). - Exécutez
sudo ss -ltnp 'sport = :PORT'(ou l’équivalent UDP) et identifiez le PID/processus. - Si c’est un conteneur :
docker pset localisez le mappage de port ; arrêtez/supprimez le conteneur approprié. - Si c’est un service hôte : trouvez l’unité avec
systemctl status PID; décidez déplacer vs arrêter. - Si c’est systemd socket : stoppez/désactivez l’unité
.socket. - Relancez votre démarrage Docker avec une adresse de bind explicite si approprié (loopback-only pour les outils internes).
- Vérifiez avec
curllocalement et depuis un hôte pair si le service doit être externe.
Checklist B: Clean fix for production (the one you can explain later)
- Inventaire des écouteurs actuels : capturez la sortie de
ss -ltnpavant les changements. - Identifier la propriété : mappez le PID à l’unité/conteneur et documentez-le dans le ticket de changement.
- Définir le port contrat : quels clients en dépendent ? Est-il dans des règles de pare-feu, allowlists, monitoring, ou DNS ?
- Choisir une stratégie :
- Déplacer le port publié du conteneur.
- Déplacer le port du service hôte.
- Introduire un reverse proxy et garder le port externe stable.
- Lier les services à des interfaces distinctes (public vs management).
- Déployer avec un plan de rollback : « Si X échoue, restaurez Y listener et revenez la conf Z. » Pas optionnel.
- Validation post-change : l’écouteur existe, l’appli répond, la surveillance passe, et aucun nouvel écouteur inattendu n’est apparu.
- Prévenir la récurrence : ajoutez un contrôle preflight des ports dans CI/CD ou la provision d’hôtes.
Checklist C: Compose-specific plan (ports are policy, not decoration)
- Trouvez le mapping dans
docker-compose.ymlsousports:. - Vérifiez les doublons entre services (erreur courante de copier/coller).
- Assurez-vous que vos ports cibles ne sont pas utilisés par d’autres stacks sur le même hôte.
- Si vous avez besoin de plusieurs instances du même stack, ne réutilisez pas les mêmes ports hôtes. Paramétrez-les.
- Envisagez de supprimer les ports publiés pour les services internes et connectez-les via les réseaux Docker à la place.
FAQ
1) Why does Docker say “port is already allocated” instead of showing the process?
Docker rapporte l’erreur du noyau résultant de l’appel bind. Le noyau ne fournit pas de « détails de propriété amicaux » dans cette réponse d’appel système.
Utilisez ss ou lsof pour identifier le PID propriétaire.
2) I ran netstat and it showed nothing, but Docker still fails. Why?
Vous vérifiez peut‑être le mauvais protocole (UDP vs TCP), la mauvaise famille d’adresses (IPv4 vs IPv6), ou vous filtrez incorrectement.
De plus, certaines versions de netstat n’affichent pas la propriété des processus sans root. Préférez ss -ltnp avec sudo.
3) Can two containers publish the same host port if they bind to different IPs?
Oui, si vous publiez explicitement sur des IP hôtes différentes, par exemple :
l’un lie 127.0.0.1:8080 et l’autre lie 192.168.1.10:8080.
Si l’un se lie à 0.0.0.0:8080, il bloque toutes les adresses IPv4 pour ce port.
4) Is it safe to just restart Docker to fix this?
Parfois cela efface des proxies orphelins ou réconcilie l’état. Mais cela interrompt aussi les conteneurs en cours et peut couper des connexions.
En production, traitez‑le comme un redémarrage de service avec une fenêtre de changement. Si vous pouvez corriger le processus propriétaire réel à la place, faites-le.
5) What if the port is held by systemd (PID 1)?
C’est généralement une unité .socket. Arrêtez et désactivez l’unité socket. Arrêter le service associé ne suffit pas car systemd garde le socket d’écoute ouvert.
6) What about TIME_WAIT—could that cause “address already in use”?
Pas pour un bind d’écoute typique. TIME_WAIT concerne des connexions récemment fermées.
L’« address already in use » habituel lors de la publication de ports Docker est un écouteur réel, pas un churn de connexions.
7) I’m using rootless Docker and can’t publish port 80. Is that the same issue?
Non. C’est généralement « permission denied » dû aux règles de binding des ports privilégiés. La correction propre est de publier un port élevé et de le frontaler avec un reverse proxy,
ou de configurer explicitement une approche basée sur les capacités (et en assumer les implications de sécurité).
8) Why does Compose sometimes fail during rolling updates with port conflicts?
Compose n’effectue pas de rolling updates comme les orchestrateurs. Si vous essayez d’exécuter « ancien » et « nouveau » conteneurs simultanément avec les mêmes ports publiés,
le second ne pourra pas binder. Utilisez des ports différents pour blue/green, ou arrêtez l’ancien conteneur avant de démarrer le nouveau.
9) How do I avoid this forever?
Vous ne pouvez pas. Mais vous pouvez réduire les surprises :
maintenez un document d’allocation de ports par hôte (ou par environnement), utilisez des reverse proxies pour des ports externes stables, et ajoutez une vérification preflight qui échoue les déploiements
lorsqu’un port requis est déjà possédé.
10) Does Kubernetes NodePort cause the same “address already in use” problem?
Cela peut arriver, mais la propriété change : kube-proxy et les écouteurs au niveau nœud peuvent réserver des ports. La méthode de diagnostic est la même : vérifiez les écouteurs avec ss,
puis mappez le propriétaire au composant de l’orchestrateur.
Conclusion : prochaines étapes qui ne vous réveilleront pas à 3h du matin
« bind: address already in use » est le noyau qui fait son travail. Votre travail est de cesser de le traiter comme une crise Docker aléatoire.
Identifiez l’écouteur avec ss, mappez‑le à une unité ou un conteneur, et choisissez une correction qui respecte les contrats et le rayon d’impact.
Prochaines étapes pratiques :
- Ajoutez un preflight de déploiement qui vérifie les ports hôtes requis avec
sset échoue vite. - Cessez de publier tout sur
0.0.0.0par défaut. Liez les outils internes au loopback. - Documentez quel service « possède » chaque port externe pertinent, et traitez les changements comme des changements d’API.
- Si des unités socket systemd sont en jeu, auditez-les — les ports peuvent être réservés même quand « le service est arrêté ».
- Quand l’état Docker et l’état du noyau divergent, redémarrez Docker seulement comme opération contrôlée, pas comme superstition.