Docker « bind: address already in use » : trouver le processus et corriger proprement

Cet article vous a aidé ?

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 -ltnp pour TCP, ss -lunp pour 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 systemd ou 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

  • curl localement, 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 :

  1. 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.
  2. L’adresse de bind compte. Un processus peut se binder sur 127.0.0.1:8080 sans bloquer un autre processus qui se binderait sur 192.168.1.10:8080 — mais un bind sur 0.0.0.0:8080 bloque toutes les adresses IPv4.
  3. Linux utilise SO_REUSEADDR et SO_REUSEPORT diffé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.
  4. TIME_WAIT n’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.
  5. 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.
  6. 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 EADDRINUSE en soi.
  7. 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é.
  8. 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.
  9. IPv6 ajoute un comportement de bind parallèle. Un service peut binder [::]:80 et couvrir aussi IPv4 via des adresses mappées IPv6 selon net.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é .service seule 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 par docker-proxy.
  • Correction : Utilisez ss -ltnp sans filtre d’abord, puis vérifiez IPv4 et IPv6. Si c’est docker-proxy, remontez au conteneur via sa ligne de commande ou docker network inspect.

2) Symptom: You stopped the service but the port is still busy

  • Cause racine : Activation de socket systemd. L’unité .socket possède le port, pas le processus que vous avez arrêté.
  • Correction : Arrêtez/désactivez l’unité socket : systemctl stop something.socket et systemctl 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.1 alors que les clients attendent un accès externe, ou le service à l’intérieur du conteneur n’écoute que 127.0.0.1 au lieu de 0.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-proxy orphelin 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)

  1. Lisez la ligne d’erreur. Notez le protocole, l’adresse et le port (0.0.0.0:PORT vs 127.0.0.1:PORT).
  2. Exécutez sudo ss -ltnp 'sport = :PORT' (ou l’équivalent UDP) et identifiez le PID/processus.
  3. Si c’est un conteneur : docker ps et localisez le mappage de port ; arrêtez/supprimez le conteneur approprié.
  4. Si c’est un service hôte : trouvez l’unité avec systemctl status PID ; décidez déplacer vs arrêter.
  5. Si c’est systemd socket : stoppez/désactivez l’unité .socket.
  6. Relancez votre démarrage Docker avec une adresse de bind explicite si approprié (loopback-only pour les outils internes).
  7. Vérifiez avec curl localement et depuis un hôte pair si le service doit être externe.

Checklist B: Clean fix for production (the one you can explain later)

  1. Inventaire des écouteurs actuels : capturez la sortie de ss -ltnp avant les changements.
  2. Identifier la propriété : mappez le PID à l’unité/conteneur et documentez-le dans le ticket de changement.
  3. Définir le port contrat : quels clients en dépendent ? Est-il dans des règles de pare-feu, allowlists, monitoring, ou DNS ?
  4. 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).
  5. Déployer avec un plan de rollback : « Si X échoue, restaurez Y listener et revenez la conf Z. » Pas optionnel.
  6. Validation post-change : l’écouteur existe, l’appli répond, la surveillance passe, et aucun nouvel écouteur inattendu n’est apparu.
  7. 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)

  1. Trouvez le mapping dans docker-compose.yml sous ports:.
  2. Vérifiez les doublons entre services (erreur courante de copier/coller).
  3. Assurez-vous que vos ports cibles ne sont pas utilisés par d’autres stacks sur le même hôte.
  4. Si vous avez besoin de plusieurs instances du même stack, ne réutilisez pas les mêmes ports hôtes. Paramétrez-les.
  5. 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 ss et échoue vite.
  • Cessez de publier tout sur 0.0.0.0 par 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.
← Précédent
Comptabilité de l’espace ZFS : pourquoi du et zfs list divergent
Suivant →
Therac-25 : quand les défaillances logicielles ont tué des patients

Laisser un commentaire