← Retour aux tutoriels

Docker : résoudre l’erreur « port déjà utilisé » et les conflits de mapping de ports

dockerportsport already in useport deja utiliseport mappingdocker composedepannagereseau

Docker : résoudre l’erreur « port déjà utilisé » et les conflits de mapping de ports

L’erreur « port déjà utilisé » (souvent visible sous la forme bind: address already in use ou Ports are not available) est l’un des problèmes Docker les plus fréquents. Elle survient quand Docker essaie d’exposer un port sur la machine hôte (votre PC/serveur) alors qu’un autre processus l’utilise déjà, ou quand un mapping de ports est incohérent (mauvaise interface, IPv4/IPv6, règles iptables, proxy Docker, etc.).

Ce tutoriel explique en profondeur :


1) Comprendre le problème : port conteneur vs port hôte

Quand vous lancez un conteneur avec :

docker run -p 8080:80 nginx:alpine

vous demandez à Docker :

Le format est :

-p [IP_HOTE:]PORT_HOTE:PORT_CONTENEUR[/PROTO]

Exemples :

Pourquoi l’erreur arrive ?

Parce que Docker doit réserver PORT_HOTE sur l’hôte. Si un autre processus écoute déjà sur ce port, Docker ne peut pas binder dessus.

Messages typiques :


2) Première vérification : quel conteneur a déjà pris le port ?

Avant d’accuser un service “hors Docker”, vérifiez si un autre conteneur utilise déjà le port.

Voir les ports publiés par les conteneurs

docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Ports}}'

Ou plus ciblé :

docker ps --format '{{.Names}} -> {{.Ports}}' | grep -E '(:|->)8080'

Inspecter un conteneur précis

docker port mon_conteneur

ou :

docker inspect --format '{{json .NetworkSettings.Ports}}' mon_conteneur | jq

Si vous trouvez un conteneur qui publie déjà 0.0.0.0:8080->80/tcp, vous avez la cause : deux conteneurs ne peuvent pas publier le même port hôte sur la même IP/protocole.


3) Identifier le processus hôte qui occupe le port (Linux)

Si aucun conteneur n’occupe le port, c’est très probablement un processus du système.

Avec ss (recommandé)

sudo ss -ltnp | grep ':8080'

Exemple de sortie :

LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=2143,fd=21))

Ici, un serveur Node écoute déjà sur 8080.

Avec lsof

sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P

Avec netstat (ancien)

sudo netstat -tulpn | grep ':8080'

4) Identifier le processus hôte (macOS)

Sur macOS :

sudo lsof -nP -iTCP:8080 -sTCP:LISTEN

Puis, si nécessaire :

ps -p <PID> -o pid,ppid,user,command

5) Identifier le processus hôte (Windows)

PowerShell : trouver le PID qui écoute sur le port

netstat -ano | findstr :8080

Vous obtiendrez un PID. Pour connaître le processus :

Get-Process -Id <PID>

Ou en une commande :

Get-Process -Id (Get-NetTCPConnection -LocalPort 8080).OwningProcess

6) Corriger : stratégies de résolution (du plus simple au plus robuste)

Stratégie A — Changer le port hôte

Si vous n’avez pas besoin de 8080 précisément, mappez vers un autre port :

docker run -p 8081:80 nginx:alpine

En Docker Compose :

docker compose up -d

puis modifiez :

# (extrait conceptuel ; ne copiez pas tel quel si votre fichier diffère)
ports:
  - "8081:80"

Idée clé : le port conteneur (80) peut rester identique, c’est le port hôte qui doit être unique.

Stratégie B — Arrêter le service qui occupe le port

Si le port est pris par un service que vous pouvez arrêter :

Linux (systemd)

sudo systemctl status nginx
sudo systemctl stop nginx
sudo systemctl disable nginx

Même logique pour Apache :

sudo systemctl stop apache2

Arrêter un processus “à la main”

Si vous avez identifié un PID :

sudo kill <PID>

Si le processus résiste :

sudo kill -9 <PID>

Attention : -9 tue brutalement. Préférez un arrêt propre quand c’est possible.

Stratégie C — Publier seulement sur 127.0.0.1 (éviter certains conflits et limiter l’exposition)

Si un port est utilisé sur une interface externe mais pas en local (cas rare, mais possible), vous pouvez binder uniquement sur loopback :

docker run -p 127.0.0.1:8080:80 nginx:alpine

Cela réduit aussi la surface d’attaque : le service n’est accessible que depuis la machine elle-même.

Stratégie D — Ne pas publier de port : utiliser un réseau Docker interne

Souvent, vous n’avez pas besoin d’exposer la base de données sur l’hôte. Exemple : PostgreSQL utilisé uniquement par une API conteneurisée.

Au lieu de :

docker run -p 5432:5432 postgres:16

faites :

docker network create mon_reseau
docker run -d --name db --network mon_reseau postgres:16
docker run -d --name api --network mon_reseau mon-image-api

L’API contactera db:5432 via DNS interne Docker, sans mapping de port hôte. Résultat : plus de conflit avec un PostgreSQL local, et meilleure sécurité.


7) Comprendre le rôle de EXPOSE (et pourquoi ça ne résout rien)

Dans un Dockerfile, vous pouvez voir :

EXPOSE 80

EXPOSE n’ouvre aucun port sur l’hôte. C’est une indication (documentation) et un “port par défaut” pour certaines options, mais le port n’est publié que via :

Donc, si vous avez un conflit, EXPOSE n’est pas la cause directe : c’est le -p.


8) Cas classique : -P et les ports aléatoires (utile pour éviter les conflits)

Si vous voulez éviter de choisir un port hôte et laisser Docker en attribuer un libre :

docker run -d -P nginx:alpine

Puis trouvez le port attribué :

docker ps --format 'table {{.Names}}\t{{.Ports}}'

ou :

docker port <container_id>

Vous verrez quelque chose comme :

80/tcp -> 0.0.0.0:49153

C’est pratique en dev, CI, ou pour lancer plusieurs instances sans collision.


9) Problèmes subtils : IPv4 vs IPv6 et “double bind”

Sur certains systèmes, un service peut écouter en IPv6 sur :::8080 et, selon la configuration, cela peut aussi réserver l’IPv4 (via v6only=0). Résultat : Docker échoue à binder 0.0.0.0:8080 même si vous ne voyez pas explicitement un listener IPv4.

Diagnostiquer

sudo ss -ltnp | grep 8080
sudo ss -ltnp6 | grep 8080

Si vous voyez :

LISTEN ... :::8080 ... users:(("java",pid=...,fd=...))

alors le port est déjà pris.

Contournement possible

Exemple :

docker run -p 127.0.0.1:8080:80 nginx:alpine

Mais si le service écoute déjà sur 127.0.0.1:8080, ça ne marchera pas.


10) “Port déjà utilisé” alors que rien n’écoute : pistes avancées

Il arrive que ss/lsof ne montrent rien, mais Docker refuse quand même. Les causes possibles :

A) Un conteneur existe encore (même arrêté) avec une réservation ?

En général, un conteneur arrêté ne garde pas le port. Mais vérifiez quand même l’ensemble :

docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

Supprimez les conteneurs inutiles :

docker rm -f mon_conteneur

B) Règles iptables/nftables incohérentes (Linux)

Docker manipule des règles NAT. Si votre firewall est complexe, ou si vous avez basculé entre iptables-legacy et iptables-nft, il peut y avoir des incohérences.

Vérifiez les règles Docker :

sudo iptables -t nat -S | grep -E 'DOCKER|docker'

Avec nftables :

sudo nft list ruleset | grep -i docker

Redémarrer Docker peut reconstruire certaines règles :

sudo systemctl restart docker

Attention : redémarrer Docker stoppe/redémarre potentiellement des conteneurs selon votre configuration.

C) Le “userland-proxy” Docker

Docker peut utiliser un proxy en espace utilisateur pour gérer certains bindings. Selon les versions/config, cela peut influencer les erreurs.

Voir la config :

docker info | grep -i proxy

Sur Linux, la configuration se trouve souvent dans :

cat /etc/docker/daemon.json

Si vous voyez userland-proxy, sachez que le comportement peut différer. Modifiez uniquement si vous maîtrisez les implications réseau.

D) WSL2 / Docker Desktop (Windows)

Avec Docker Desktop, la couche réseau passe par une VM. Un port peut être “réservé” par Windows (ex. Hyper-V, IIS, services système), ou par un autre backend.


11) Docker Compose : conflits fréquents et bonnes pratiques

A) Deux services publient le même port

Exemple typique :

Solution : ports hôtes distincts :

B) Plusieurs projets Compose en parallèle

Deux projets différents (deux dossiers) peuvent publier le même port. Même si les réseaux sont séparés, le port hôte est global.

Diagnostiquer :

docker ps --format 'table {{.Names}}\t{{.Ports}}'

Arrêter un projet Compose :

docker compose down

Si vous avez plusieurs contextes ou noms de projet, listez :

docker compose ls

Puis :

docker compose -p <nom_projet> down

C) Utiliser des variables d’environnement pour rendre les ports configurables

Dans un fichier .env (Compose lit souvent ce fichier automatiquement) :

WEB_PORT=8080

Puis dans Compose (conceptuellement) :

ports:
  - "${WEB_PORT}:80"

Ainsi, vous pouvez lancer le même stack avec un autre port sans modifier le fichier principal.

D) Éviter d’exposer inutilement

Bon modèle :

Vous réduisez les conflits et augmentez la sécurité.


12) Scénarios concrets (avec commandes) et résolutions

Scénario 1 — Nginx local vs Nginx Docker sur le port 80

Vous lancez :

docker run --rm -p 80:80 nginx:alpine

Erreur : port 80 déjà utilisé.

Diagnostiquer :

sudo ss -ltnp | grep ':80'

Si c’est Nginx système :

sudo systemctl stop nginx

Ou mappez ailleurs :

docker run --rm -p 8080:80 nginx:alpine

Scénario 2 — PostgreSQL local (5432) et conteneur PostgreSQL

Vous lancez :

docker run --rm -p 5432:5432 postgres:16

Mais vous avez déjà PostgreSQL en local.

Option 1 : ne pas publier, si usage interne :

docker run -d --name db postgres:16

Option 2 : publier sur un autre port hôte :

docker run --rm -p 5433:5432 postgres:16

Puis se connecter sur localhost:5433.

Scénario 3 — Port occupé par un autre conteneur “oublié”

Vous lancez une appli sur 8080, erreur.

Vérifiez :

docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep 8080

Stoppez et supprimez :

docker stop ancien_service
docker rm ancien_service

Ou changez votre port.


13) Prévenir les conflits : méthodes fiables en environnement dev, CI et prod

A) Standardiser une matrice de ports par projet

Exemple :

Documentez-le dans un README et utilisez .env.

B) Utiliser un reverse proxy (Traefik, Nginx) et des noms d’hôtes

Au lieu de publier plein de ports, publiez seulement 80/443 pour un proxy, et routez par hostname.

Principe :

Vous évitez d’empiler 8080, 8081, 8082, etc.

C) Préférer 127.0.0.1 en dev

Limiter l’exposition :

docker run -p 127.0.0.1:8080:80 mon_app

D) En prod : privilégier l’orchestration et les ports “standards”

En production, on publie généralement :

Le reste reste interne (réseaux privés, security groups, etc.).


14) Aide-mémoire (cheat sheet)

Trouver qui écoute sur un port (Linux)

sudo ss -ltnp | grep ':8080'
sudo lsof -iTCP:8080 -sTCP:LISTEN -n -P

Voir les ports Docker

docker ps --format 'table {{.Names}}\t{{.Ports}}'
docker port <nom>

Libérer le port

sudo systemctl stop <service>
sudo kill <PID>

Éviter le conflit

docker run -p 8081:80 <image>
docker run -P <image>
docker run -p 127.0.0.1:8080:80 <image>

Nettoyer des conteneurs

docker ps -a
docker rm -f <nom>

15) Conclusion

Résoudre « port déjà utilisé » avec Docker revient presque toujours à répondre à trois questions, dans l’ordre :

  1. Quel port hôte Docker essaie-t-il d’ouvrir ? (regarder votre -p / ports:)
  2. Qui l’utilise déjà ? (un autre conteneur via docker ps, ou un processus hôte via ss/lsof/netstat)
  3. Quelle stratégie est la plus saine ?
    • changer le port hôte,
    • arrêter le service concurrent,
    • ne pas publier le port (réseau interne),
    • ou centraliser via un reverse proxy.

Avec les commandes de diagnostic ci-dessus et une compréhension claire de la différence port conteneur vs port hôte, vous pourrez traiter aussi bien les cas simples que les situations plus subtiles (IPv6, firewall, Docker Desktop/WSL2, etc.).