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 :
- comment fonctionne le mapping de ports Docker (NAT, iptables, userland-proxy),
- comment diagnostiquer précisément quel processus occupe le port,
- comment corriger les conflits (changer de port, arrêter le service concurrent, utiliser des réseaux internes, etc.),
- comment éviter les conflits dans Docker Compose,
- et comment traiter des cas plus subtils (IPv6, ports “fantômes”, services systemd, WSL2, rootless Docker).
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 :
- d’exposer le port 80 dans le conteneur (où écoute Nginx),
- sur le port 8080 de la machine hôte.
Le format est :
-p [IP_HOTE:]PORT_HOTE:PORT_CONTENEUR[/PROTO]
Exemples :
-p 127.0.0.1:8080:80: accessible uniquement en local-p 0.0.0.0:8080:80: accessible sur toutes les interfaces IPv4-p [::1]:8080:80: accessible en IPv6 local-p 8080:80/tcp: explicite TCP (par défaut)-p 5353:5353/udp: UDP
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 :
Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in useBind for 0.0.0.0:5432 failed: port is already allocatedPorts are not available: exposing port TCP 0.0.0.0:80 -> 0.0.0.0:0: listen tcp 0.0.0.0:80: bind: address already in use
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'
-l: listening-t: TCP-n: numérique (pas de DNS)-p: affiche le processus (PID/nom)
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 :
-9tue 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 :
-p/--publish-P/--publish-all(publie sur des ports aléatoires de l’hôte)
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
- changer de port,
- arrêter le service,
- ou binder Docker sur une IP spécifique (si le service n’écoute pas sur cette IP).
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.
- Vérifiez côté Windows (PowerShell) :
netstat -ano - Vérifiez côté Docker :
docker ps - Redémarrez Docker Desktop si nécessaire.
11) Docker Compose : conflits fréquents et bonnes pratiques
A) Deux services publient le même port
Exemple typique :
webpublie8080:80adminpublie aussi8080:80
Solution : ports hôtes distincts :
8080:80et8081:80
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 :
- exposer seulement le reverse proxy (ex. 80/443),
- garder DB/Redis/RabbitMQ internes.
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 :
- Projet A : 8080, 5432, 6379
- Projet B : 9080, 6432, 7379
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 :
app1.local-> conteneur app1:80 (interne)app2.local-> conteneur app2:80 (interne)
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 :
- 80/443 (HTTP/HTTPS),
- éventuellement quelques ports d’admin, mais contrôlés par firewall.
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 :
- Quel port hôte Docker essaie-t-il d’ouvrir ? (regarder votre
-p/ports:) - Qui l’utilise déjà ? (un autre conteneur via
docker ps, ou un processus hôte viass/lsof/netstat) - 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.).