À la fin de cette page, tu seras capable de :
hôte:conteneurEXPOSE dans un Dockerfile de -p dans docker runPORT_HÔTE:PORT_CONTENEUR crée un "tunnel" entre ta machine et le conteneurEXPOSE — Simple documentation dans le Dockerfile ; c'est -p qui ouvre réellement le port
Une machine connectée au réseau peut faire tourner plusieurs services en même temps : un serveur web, une base de données, un serveur SSH, un serveur mail...
Pour différencier ces services, on utilise les ports : des numéros de 0 à 65535 associés à chaque connexion réseau.
Analogie : pense à une adresse postale d'immeuble.
Quand tu tapes http://localhost:8080 dans ton navigateur :
localhost = ta propre machine (adresse IP 127.0.0.1)8080 = le port, c'est-à-dire le "bureau" précis auquel tu frappesCertains ports sont réservés par convention internationale pour des services précis :
| Port | Service | Protocole |
|---|---|---|
| 80 | HTTP (web non sécurisé) | TCP |
| 443 | HTTPS (web sécurisé) | TCP |
| 22 | SSH (accès distant) | TCP |
| 21 | FTP (transfert de fichiers) | TCP |
| 25 | SMTP (envoi d'emails) | TCP |
| 3306 | MySQL / MariaDB | TCP |
| 5432 | PostgreSQL | TCP |
| 6379 | Redis (cache) | TCP |
| 27017 | MongoDB | TCP |
| 3000 | Node.js / apps web modernes (convention) | TCP |
| 8080 | HTTP alternatif (souvent utilisé en dev) | TCP |
| 9000 | PHP-FPM / divers | TCP |
Les ports 0 à 1023 sont dits "privilégiés" : sur Linux, seul root peut les utiliser. C'est pourquoi en développement on utilise souvent des ports comme 8080 ou 8443 au lieu de 80 et 443.
Chaque conteneur Docker est isolé du système hôte. Il possède sa propre interface réseau virtuelle, ses propres ports.
Quand Nginx démarre dans un conteneur, il écoute sur le port 80 du conteneur. Mais ce port 80 n'est pas visible depuis ta machine par défaut.
┌─────────────────────────────────┐
│ Ta machine (hôte) │
│ │
│ ┌──────────────────────────┐ │
│ │ Conteneur Nginx │ │
│ │ │ │
│ │ Nginx écoute sur :80 🔒 │ │
│ │ (invisible de l'hôte) │ │
│ └──────────────────────────┘ │
└─────────────────────────────────┘
→ http://localhost:80 ❌ (rien n'écoute côté hôte)
Pour rendre ce port accessible, il faut créer un mapping entre un port de ta machine et le port du conteneur.
PORT_HÔTE:PORT_CONTENEURL'option -p de docker run crée ce tunnel :
docker run -p 8080:80 nginx
┌──────────────────────────────────────────────┐
│ Ta machine (hôte) │
│ │
│ Port 8080 ──────────────────────────────┐ │
│ (ouvert) │ │
│ ┌─────────────────────┐ │ │
│ │ Conteneur Nginx │ │ │
│ │ │◄──┘ │
│ │ Nginx écoute :80 │ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────┘
→ http://localhost:8080 ✅ (ta machine → conteneur)
Lecture de la syntaxe : toujours lire de gauche à droite
-p 8080:80
↑ ↑
│ └── Port dans le conteneur (Nginx écoute ici)
└──────── Port sur ta machine (tu accèdes par ici)
Cas 1 — Le port est déjà pris sur la machine hôte
Si tu as déjà Apache ou IIS qui écoute sur le port 80 de ta machine, tu ne peux pas démarrer un deuxième service sur ce même port. Tu utilises un port libre :
# Apache utilise déjà le port 80 sur la machine hôte
docker run -p 8080:80 nginx # Nginx accessible sur :8080
docker run -p 8081:80 apache # Apache conteneur accessible sur :8081
Cas 2 — Tu fais tourner plusieurs instances du même service
# Trois serveurs PHP pour trois projets différents
docker run -p 8001:80 projet-alpha
docker run -p 8002:80 projet-beta
docker run -p 8003:80 projet-gamma
Chaque projet est accessible sur un port différent :
Cas 3 — Le même port dans plusieurs conteneurs
Dans le conteneur, le port ne change pas (c'est toujours le port natif de l'application). Seul le port hôte doit être unique.
# Deux bases MySQL, chacune sur son port hôte
docker run -p 3306:3306 --name db-dev mysql:8.0 # dev
docker run -p 3307:3306 --name db-test mysql:8.0 # test
La connexion à localhost:3307 atteint le conteneur db-test, même si MySQL écoute toujours sur le port 3306 à l'intérieur.
Si tu essaies de mapper deux services sur le même port hôte, Docker refuse avec une erreur claire :
docker run -p 8080:80 nginx # ✅ Premier service : OK
docker run -p 8080:80 apache # ❌ Erreur !
Error response from daemon: driver failed programming external connectivity
on endpoint apache: Bind for 0.0.0.0:8080 failed:
port is already allocated
Solution : changer le port hôte du deuxième service.
Un même conteneur peut exposer plusieurs ports :
# Gitea : port web + port SSH Git
docker run -p 3000:3000 -p 2222:22 gitea/gitea
# Même chose en Docker Compose
services:
gitea:
image: gitea/gitea
ports:
- "3000:3000" # interface web
- "2222:22" # SSH pour git push/pull
Par défaut, -p 8080:80 expose le port sur toutes les interfaces réseau de ta machine (0.0.0.0). Cela signifie que les autres machines sur le réseau local peuvent aussi y accéder.
Pour restreindre l'accès à ta machine uniquement (loopback) :
# Accessible uniquement depuis localhost, pas depuis le réseau
docker run -p 127.0.0.1:8080:80 nginx
# En Compose
ports:
- "127.0.0.1:8080:80"
C'est une bonne pratique pour les services sensibles (base de données, admin) qui ne doivent pas être accessibles depuis le réseau local ou Internet.
# ✅ Bonne pratique pour MySQL : jamais exposé sur le réseau
-p 127.0.0.1:3306:3306
# ⚠️ À éviter en production : MySQL visible sur tout le réseau
-p 3306:3306
EXPOSE dans le Dockerfile vs -p dans docker runC'est une confusion très fréquente.
EXPOSE — Documentation seulementFROM nginx
EXPOSE 80
EXPOSE dans un Dockerfile ne publie pas le port. Il sert uniquement de documentation : il indique aux utilisateurs de l'image sur quel port le service écoute.
C'est l'équivalent d'un commentaire : "Ce conteneur écoute sur le port 80."
-p — Ce qui ouvre réellement le portdocker run -p 8080:80 nginx
C'est -p (ou ports: dans Compose) qui crée réellement le mapping et rend le port accessible depuis ta machine.
-P majuscule — Mapping automatiquedocker run -P nginx
Le -P majuscule publie automatiquement tous les ports déclarés avec EXPOSE dans le Dockerfile, sur des ports hôtes aléatoires (ex: 0.0.0.0:32768->80/tcp).
Peu utilisé en pratique, mais pratique pour tester rapidement une image inconnue.
| Publie le port ? | Utilisation | |
|---|---|---|
EXPOSE 80 (Dockerfile) |
❌ Non | Documentation uniquement |
docker run -p 8080:80 |
✅ Oui | Mapping explicite (recommandé) |
docker run -P |
✅ Oui | Mapping automatique sur ports aléatoires |
Dans un fichier docker-compose.yml, les ports se déclarent sous ports: :
services:
web:
image: nginx:1.25
ports:
- "8080:80" # hôte:conteneur (avec guillemets recommandés)
- "127.0.0.1:443:443" # restreint à localhost
💡 Pourquoi les guillemets ? En YAML,
80:80pourrait être interprété comme un nombre (base 60 en vieux YAML). Les guillemets évitent toute ambiguïté.
Quand deux services sont dans le même réseau Docker Compose, ils se contactent directement par leur port interne, sans passer par le port hôte.
services:
app:
image: php:8.3-apache
ports:
- "8080:80" # exposé vers l'extérieur
db:
image: mysql:8.0
# Pas de "ports:" ici → MySQL n'est PAS accessible depuis ta machine
# Mais "app" peut le contacter via le réseau Docker interne
Dans le code PHP du conteneur app :
// On utilise le port INTERNE du conteneur db (3306)
// pas le port hôte
$pdo = new PDO("mysql:host=db;port=3306;dbname=...", ...);
C'est une bonne pratique de sécurité : ne pas exposer les ports de base de données vers l'extérieur si ce n'est pas nécessaire.
Ta machine ──:8080──► app ──:3306──► db
↑ ↑
port hôte port interne
(visible) (invisible depuis ta machine)

TA MACHINE (hôte)
┌───────────────────────────────────────────────────────┐
│ │
│ :80 → (LIBRE ou pris par Apache/IIS) │
│ :8080 ──────────────────────────────────────────┐ │
│ :8081 ──────────────────────────────┐ │ │
│ :3306 → (non exposé / loopback) │ │ │
│ │ │ │
│ ┌───────────────────────┐ ┌───┴────────────┐ │
│ │ Conteneur MySQL │ │ Conteneur PHP │ │
│ │ │ │ │ │
│ │ écoute sur :3306 ◄──┼───┤ écoute sur :80 │ │
│ │ (réseau interne) │ │ (mapped :8080) │ │
│ └───────────────────────┘ └────────────────┘ │
│ │
└───────────────────────────────────────────────────────┘
http://localhost:8080 → conteneur PHP ✅
mysql://localhost:3306 → ❌ non exposé (sécurité)
mysql://db:3306 → ✅ depuis l'intérieur du réseau Docker
| Syntaxe | Signification |
|---|---|
-p 8080:80 |
Port 8080 hôte → port 80 conteneur |
-p 3306:3306 |
Même port des deux côtés |
-p 127.0.0.1:8080:80 |
Accessible uniquement depuis localhost |
-P |
Publie tous les EXPOSE sur ports aléatoires |
ports: ["8080:80"] |
Équivalent Compose de -p 8080:80 |
Pas de ports: |
Le service n'est accessible que depuis le réseau Docker interne |