Docker pour les développeurs PHP : setup local et bonnes pratiques
Docker remplace WAMP, XAMPP et MAMP par un environnement de développement isolé, reproductible et documenté. Chaque projet a sa propre version de PHP, ses propres extensions, sa propre configuration Apache — sans conflit avec les autres projets. En 2026, la quasi-totalité des équipes de développement utilisent Docker pour leurs environnements locaux. Ce guide couvre la mise en place d'un setup Docker complet pour un projet PHP avec Apache, Node.js et MailHog.
Architecture d'un environnement Docker PHP
Le Dockerfile : construire l'image PHP
Le Dockerfile décrit précisément l'environnement serveur. C'est la documentation vivante de votre configuration — chaque extension PHP, chaque module Apache, chaque outil installé est versionné et reproductible.
FROM php:8.5-apache
# Extensions PHP nécessaires
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd zip intl mbstring xml opcache
# Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Node.js via nvm (pour Webpack/Vite)
ENV NVM_DIR=/usr/local/nvm
RUN mkdir -p $NVM_DIR \
&& curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh \
| NVM_DIR=$NVM_DIR bash \
&& . "$NVM_DIR/nvm.sh" && nvm install 24
# Apache : activer les modules
RUN a2enmod rewrite headers expires deflate ssl
# SSL auto-signé (développement)
RUN openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/ssl-cert-snakeoil.key \
-out /etc/ssl/certs/ssl-cert-snakeoil.pem \
-subj "/CN=localhost"
WORKDIR /var/www/html
EXPOSE 80 443
Points importants :
- Image officielle
php:8.5-apache— Inclut Apache avec mod_php, pas besoin d'un conteneur Nginx séparé. - Extensions compilées —
docker-php-ext-installgère la compilation. Pour Xdebug et Redis, utiliserpecl install. - Composer via multi-stage —
COPY --from=composer:latestcopie le binaire sans installer les dépendances de Composer. - Node.js via nvm — Nécessaire si le projet utilise Webpack ou Vite pour compiler les assets.
Docker Compose : orchestrer les services
# docker-compose.yml
services:
web:
build: .
ports:
- "8080:80"
- "8443:443"
volumes:
- .:/var/www/html
depends_on:
- mailhog
mailhog:
image: mailhog/mailhog:latest
ports:
- "8025:8025" # Interface web
- "1025:1025" # SMTP
Le volumes: .:/var/www/html monte le code source local dans le conteneur. Chaque modification de fichier est immédiatement visible — pas besoin de rebuild.
Services additionnels courants
- MySQL / MariaDB — Ajouter un service
dbavec un volume persistant pour les données. - Redis — Cache objet pour les applications Symfony ou Laravel.
- PhpMyAdmin — Interface web pour la base de données en développement.
- Elasticsearch — Pour les boutiques PrestaShop ou Sylius avec recherche avancée.
Gestion des ports : éviter les conflits entre projets
Si vous travaillez sur plusieurs projets Docker en parallèle, le problème classique arrive vite : deux projets essaient d'utiliser le même port (8080, 3306, 8443...) et Docker refuse de démarrer le second avec l'erreur Bind for 0.0.0.0:8080 failed: port is already allocated.
Solution : ports configurables via .env
La bonne pratique est de ne jamais hardcoder les ports dans le docker-compose.yml mais d'utiliser des variables d'environnement avec des valeurs par défaut :
# docker-compose.yml
services:
web:
ports:
- "${APP_PORT:-8080}:80"
- "${APP_SSL_PORT:-8443}:443"
db:
ports:
- "${DB_PORT:-3306}:3306"
mailhog:
ports:
- "${MAILHOG_PORT:-8025}:8025"
Chaque projet a son propre fichier .env avec des ports uniques :
# Projet A (.env)
APP_PORT=8080
APP_SSL_PORT=8443
DB_PORT=3306
# Projet B (.env)
APP_PORT=8180
APP_SSL_PORT=8543
DB_PORT=3307
# Projet C (.env)
APP_PORT=8280
APP_SSL_PORT=8643
DB_PORT=3308
Convention de plages de ports par projet
Pour les équipes ou les développeurs qui jonglent entre plusieurs projets, attribuer une plage de ports dédiée à chaque projet évite les collisions :
- Projet A : ports 8000-8099 (web 8080, SSL 8043, DB 8006, mail 8025)
- Projet B : ports 8100-8199 (web 8180, SSL 8143, DB 8106, mail 8125)
- Projet C : ports 8200-8299 (web 8280, SSL 8243, DB 8206, mail 8225)
Diagnostiquer un conflit de port
Si Docker refuse de démarrer, identifier quel processus occupe le port :
# Linux / macOS / WSL
sudo lsof -i :8080
sudo ss -tlnp | grep 8080
# Lister les conteneurs Docker et leurs ports
docker ps --format "{{.Names}}: {{.Ports}}" | grep 8080
Solution avancée : Traefik comme reverse proxy
Quand le nombre de projets grandit, gérer des plages de ports manuellement devient fastidieux. Traefik est un reverse proxy conçu pour Docker qui résout le problème élégamment : tous les projets utilisent le port 80/443 et Traefik route le trafic vers le bon conteneur en fonction du nom de domaine.
# docker-compose.yml (Traefik partagé entre tous les projets)
services:
traefik:
image: traefik:v3
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command:
- --providers.docker=true
- --entrypoints.web.address=:80
Chaque projet déclare son domaine via des labels Docker, sans exposer de port :
# docker-compose.yml (projet A)
services:
web:
labels:
- "traefik.http.routers.projet-a.rule=Host(`projet-a.localhost`)"
networks:
- traefik
# docker-compose.yml (projet B)
services:
web:
labels:
- "traefik.http.routers.projet-b.rule=Host(`projet-b.localhost`)"
networks:
- traefik
Résultat : http://projet-a.localhost et http://projet-b.localhost fonctionnent simultanément sur le même port 80, sans conflit. Traefik détecte automatiquement les conteneurs qui démarrent et les ajoute au routing. C'est la solution recommandée pour les développeurs qui travaillent sur 3+ projets simultanément.
Astuce : ne pas exposer les ports inutilement
Les conteneurs qui communiquent entre eux (web → db, web → mailhog) n'ont pas besoin de ports exposés sur l'hôte. Docker Compose crée un réseau interne où les services se contactent par leur nom (db:3306, mailhog:1025). N'exposez un port que si un humain a besoin d'y accéder (l'interface web MailHog, PhpMyAdmin, le site lui-même).
Le Makefile : simplifier le quotidien
Taper docker compose exec web bash -c "source /usr/local/nvm/nvm.sh && npm run prod" à chaque fois est fastidieux. Un Makefile encapsule les commandes Docker dans des raccourcis lisibles :
# Makefile
start: ## Premier lancement
docker compose up -d --build
docker compose exec -T web composer install
docker compose exec -T web npm install
docker compose exec -T web npm run prod
up: ## Démarrer
docker compose up -d
down: ## Arrêter
docker compose down
shell: ## Terminal dans le conteneur
docker compose exec web bash
npm-prod: ## Compiler les assets
docker compose exec web npm run prod
phpstan: ## Analyse statique
docker compose exec web vendor/bin/phpstan analyse
test: ## Tests PHPUnit
docker compose exec web vendor/bin/phpunit
L'équipe n'a pas besoin de connaître Docker en détail — make start installe tout, make up démarre, make test lance les tests.
MailHog : tester les emails sans SMTP
MailHog intercepte tous les emails envoyés par l'application et les affiche dans une interface web à http://localhost:8025. L'application envoie sur le port SMTP 1025 au lieu du serveur de production — aucun email n'atteint de vraies boîtes aux lettres.
Configuration dans l'application PHP :
// Détection automatique de l'environnement Docker
if (file_exists('/.dockerenv')) {
$transport = Transport::fromDsn('smtp://mailhog:1025');
} else {
$transport = new SendmailTransport();
}
WSL2 sur Windows : configuration critique
Docker Desktop sur Windows utilise WSL2 (Windows Subsystem for Linux) comme moteur. La performance dépend de l'emplacement des fichiers :
- Filesystem Linux (
/home/user/projects/) — Performances natives, opérations fichiers rapides. C'est ici qu'il faut travailler. - Filesystem Windows monté (
/mnt/c/Users/...) — 5 à 10 fois plus lent. Lesnpm installetcomposer installsont pénibles. À éviter.
Pour migrer un projet existant : copier le code dans le filesystem WSL (cp -r /mnt/c/www/monprojet ~/projects/) et travailler depuis là. VS Code supporte nativement WSL via l'extension "WSL" — l'éditeur s'ouvre sur Windows mais les fichiers sont dans Linux.
Bonnes pratiques Docker pour PHP
Images et Dockerfile
- Versions explicites —
php:8.5-apacheau lieu dephp:latest. Évite les surprises quand une nouvelle version sort. - Extensions pinnées —
pecl install xdebug-3.3.2au lieu depecl install xdebug. Garantit la compatibilité. - Multi-stage builds — Séparer l'image dev (avec Xdebug, Node.js) de l'image prod (minimale). L'image prod est plus légère et plus sécurisée.
- .dockerignore — Exclure
vendor/,node_modules/,.git/du contexte de build pour accélérer la construction.
Docker Compose
- Variables d'environnement — Utiliser un fichier
.envpour les ports et credentials. Ne pas hardcoder dansdocker-compose.yml. - Volumes nommés pour les données persistantes (MySQL). Les données survivent à un
docker compose down. - Healthcheck — Ajouter un healthcheck sur le conteneur web pour que Docker Compose sache quand le service est prêt.
Workflow quotidien
make upen début de journée — Démarre les conteneurs- Développer normalement — Les fichiers sont synchronisés en temps réel via le volume
make npm-devsi vous modifiez du SCSS/JS — Webpack watch dans le conteneurmake testavant chaque commit — PHPUnit dans le conteneurmake downen fin de journée — Arrête les conteneurs, libère les ressources
Intégration avec la CI/CD
L'environnement Docker local et la CI/CD partagent le même objectif : un environnement reproductible. La CI utilise les mêmes versions de PHP, les mêmes extensions et les mêmes outils que le Docker local. Si ça passe en local, ça passe en CI.
Le Makefile sert de pont : make phpstan exécute la même commande en local (via Docker) et en CI (directement sur le runner). La parité dev/CI réduit les "ça marche chez moi".
Questions fréquentes
Pourquoi utiliser Docker plutôt que WAMP/XAMPP/MAMP ?
Docker isole chaque projet dans son propre conteneur avec sa version de PHP, ses extensions et sa configuration. Fini les conflits entre projets qui nécessitent des versions PHP différentes. L'environnement est reproductible : un collègue qui clone le repo et lance docker compose up a exactement le même setup. Et le Dockerfile documente précisément la configuration serveur.
Docker est-il lent sur Windows ?
Docker sur Windows via WSL2 est performant à condition de travailler dans le filesystem Linux (/home/user/), pas dans le filesystem Windows monté (/mnt/c/). La différence de performance est significative — les opérations fichiers sont 5 à 10 fois plus rapides sur le filesystem WSL2 natif.
Faut-il Docker en production ?
Pas obligatoirement. Docker est un outil de développement avant tout. En production, un hébergement classique (Apache/PHP sur un serveur dédié ou mutualisé) fonctionne très bien pour un site PHP. Docker en production est pertinent pour les architectures microservices ou les déploiements Kubernetes, pas pour un site vitrine ou une boutique e-commerce standard.
Comment débugger avec Xdebug dans Docker ?
Installer Xdebug dans le Dockerfile (pecl install xdebug), configurer xdebug.mode=debug et xdebug.client_host=host.docker.internal (port 9003). Sur Linux, ajouter extra_hosts: host.docker.internal:host-gateway dans le docker-compose.yml. Configurer l'IDE (VS Code ou PhpStorm) pour écouter sur le port 9003.
