« Chez moi, ça marche ! » Cette phrase, Docker l’a rendue obsolète. Dans ce guide Docker pour développeur web, tu montes en 5 minutes un environnement complet PHP 8.4 + Apache + MySQL + phpMyAdmin avec un seul fichier compose.yaml. Chaque ligne est expliquée, pour que tu comprennes ce que tu lances et que tu puisses l’adapter à tous tes projets.
Tu installes WampServer sur ton PC, ton collègue utilise MAMP sur son Mac, le serveur de production tourne sous Linux… et chacun a une version différente de PHP et de MySQL. Résultat : des bugs qui n’apparaissent que chez l’un ou chez l’autre. Docker pour développeur web, c’est la solution à ce problème : ton environnement est décrit dans un fichier, et il est identique sur toutes les machines.

Dans ce guide, on construit pas à pas un environnement PHP et MySQL complet. Pas besoin de connaître Docker : chaque notion est expliquée au moment où on en a besoin.

- Un serveur Apache + PHP 8.4 avec les extensions courantes.
- Une base de données MySQL 8.4 dont les données survivent aux redémarrages.
- phpMyAdmin pour gérer ta base depuis le navigateur.
- Un environnement qui démarre avec une seule commande :
docker compose up -d. - Un modèle réutilisable pour tous tes projets PHP.
Docker en 2 minutes : les notions essentielles
Avant de taper la moindre commande, voici les quatre mots à connaître :
| Notion | Définition simple | Analogie |
|---|---|---|
| Image | Un modèle figé qui contient un système et des logiciels (par exemple PHP 8.4 + Apache) | Une recette de cuisine |
| Conteneur | Une image en cours d’exécution, isolée du reste de ton système | Le plat préparé à partir de la recette |
| Volume | Un espace de stockage qui survit à la suppression du conteneur | Le congélateur où tu gardes les restes |
| Docker Compose | Un fichier qui décrit plusieurs conteneurs et la façon dont ils communiquent | Le menu complet du repas |
Une machine virtuelle embarque un système d’exploitation complet (plusieurs Go, démarrage en minutes). Un conteneur partage le noyau du système hôte et n’embarque que le nécessaire : il démarre en quelques secondes et consomme beaucoup moins de ressources. C’est pour ça qu’on peut lancer trois conteneurs sur un simple ordinateur portable.
Étape 1 : installer Docker
Sous Windows et macOS : Docker Desktop
- Télécharge Docker Desktop depuis le site officiel de Docker.
- Sous Windows, l’installateur active WSL 2 (le sous-système Linux de Windows) : accepte et redémarre si demandé.
- Lance Docker Desktop et attends que l’icône de la baleine indique que Docker est démarré.
Docker Desktop est gratuit pour un usage personnel, l’éducation, les projets open source et les petites entreprises (moins de 250 employés et moins de 10 millions de dollars de chiffre d’affaires annuel). Au-delà, un abonnement payant est requis. Docker Engine sous Linux, lui, reste entièrement gratuit.
Sous Linux : Docker Engine
# Script officiel d'installation (Ubuntu, Debian, Fedora…)
curl -fsSL https://get.docker.com | sh
# Utiliser docker sans sudo (déconnecte-toi puis reconnecte-toi ensuite)
sudo usermod -aG docker $USERBashVérifier l’installation
docker --version
docker compose version
docker run --rm hello-worldBashSi le message Hello from Docker! s’affiche, tout fonctionne.
Étape 2 : créer la structure du projet
mon-projet/
├── compose.yaml ← la description de l'environnement
├── .env ← les mots de passe (hors de Git)
├── docker/
│ └── php/
│ └── Dockerfile ← notre image PHP personnalisée
└── src/
└── index.php ← ton code PHPTextemkdir -p mon-projet/docker/php mon-projet/src
cd mon-projetBashÉtape 3 : l’image PHP personnalisée (Dockerfile)
L’image officielle php:8.4-apache contient PHP et Apache, mais pas l’extension MySQL. On crée donc notre propre image à partir d’elle, avec un Dockerfile :
# docker/php/Dockerfile
FROM php:8.4-apache
# Paquets système nécessaires à certaines extensions
RUN apt-get update && apt-get install -y --no-install-recommends \
libicu-dev libzip-dev libpng-dev unzip \
&& rm -rf /var/lib/apt/lists/*
# Extensions PHP : MySQL, internationalisation, zip, images
RUN docker-php-ext-install pdo_mysql mysqli intl zip gd opcache
# Configuration de développement (affichage des erreurs)
RUN cp "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
# Réécriture d'URL Apache (indispensable pour la plupart des frameworks)
RUN a2enmod rewrite
# Composer, copié depuis son image officielle
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/htmlDockerfile| Instruction | Rôle |
|---|---|
FROM | L’image de départ (PHP 8.4 avec Apache, sur Debian) |
RUN | Exécute une commande pendant la construction de l’image |
docker-php-ext-install | Script fourni par l’image officielle pour compiler les extensions PHP |
COPY --from= | Copie un fichier depuis une autre image (ici, le binaire Composer) |
WORKDIR | Le dossier courant dans le conteneur |
Chaque instruction RUN crée une couche dans l’image. En regroupant l’installation et le nettoyage (rm -rf /var/lib/apt/lists/*) dans la même couche, l’image finale est plus légère.
Étape 4 : les mots de passe dans le fichier .env
Docker Compose lit automatiquement un fichier .env placé à côté de compose.yaml. On y met les valeurs sensibles :
# .env
MYSQL_ROOT_PASSWORD=root_motdepasse_local
MYSQL_DATABASE=boutique
MYSQL_USER=app
MYSQL_PASSWORD=app_motdepasse_localINIEt on l’exclut de Git :
# .gitignore
.envTextePense à fournir un fichier .env.example (sans les vrais mots de passe) pour que tes collègues sachent quelles variables définir.
Étape 5 : le fichier compose.yaml
C’est le cœur de l’environnement. Il décrit les trois services et la façon dont ils se connectent :
# compose.yaml
services:
web:
build: ./docker/php
ports:
- "8080:80"
volumes:
- ./src:/var/www/html
environment:
DB_HOST: db
DB_NAME: ${MYSQL_DATABASE}
DB_USER: ${MYSQL_USER}
DB_PASSWORD: ${MYSQL_PASSWORD}
depends_on:
db:
condition: service_healthy
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
ports:
- "127.0.0.1:3307:3306"
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 5s
timeout: 5s
retries: 20
phpmyadmin:
image: phpmyadmin:latest
ports:
- "8081:80"
environment:
PMA_HOST: db
depends_on:
- db
volumes:
db_data:YAMLDécortiquons les lignes importantes :
| Ligne | Explication |
|---|---|
build: ./docker/php | Construit l’image à partir de notre Dockerfile |
ports: "8080:80" | Le port 80 du conteneur est accessible sur localhost:8080 de ton ordinateur |
volumes: ./src:/var/www/html | Ton dossier src est partagé avec le conteneur : tes modifications sont visibles immédiatement |
DB_HOST: db | Entre conteneurs, on se joint par le nom du service : la base s’appelle simplement db |
depends_on + service_healthy | Le serveur web attend que MySQL soit réellement prêt avant de démarrer |
db_data:/var/lib/mysql | Un volume nommé : tes données MySQL survivent à l’arrêt et à la suppression des conteneurs |
127.0.0.1:3307:3306 | MySQL est joignable depuis ton poste (port 3307), mais pas depuis le réseau local |
healthcheck | Docker vérifie toutes les 5 secondes que MySQL répond |
Sans cette précision, Docker publie le port sur toutes les interfaces réseau : n’importe qui sur ton Wi-Fi pourrait tenter de se connecter à ta base. En le limitant à 127.0.0.1, seul ton ordinateur y a accès. On utilise aussi le port 3307 pour ne pas entrer en conflit avec un MySQL déjà installé sur ton poste.
Étape 6 : un fichier PHP de test
<?php
// src/index.php
declare(strict_types=1);
$dsn = sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4', getenv('DB_HOST'), getenv('DB_NAME'));
try {
$pdo = new PDO($dsn, getenv('DB_USER'), getenv('DB_PASSWORD'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->exec('CREATE TABLE IF NOT EXISTS visites (
id INT AUTO_INCREMENT PRIMARY KEY,
vue_le DATETIME DEFAULT CURRENT_TIMESTAMP
)');
$pdo->exec('INSERT INTO visites () VALUES ()');
$total = $pdo->query('SELECT COUNT(*) FROM visites')->fetchColumn();
$mysql = $pdo->query('SELECT VERSION()')->fetchColumn();
echo '<h1>Docker fonctionne 🐳</h1>';
echo '<p>PHP ' . PHP_VERSION . ' · MySQL ' . htmlspecialchars($mysql) . '</p>';
echo '<p>Cette page a été vue ' . (int) $total . ' fois.</p>';
} catch (PDOException $e) {
http_response_code(500);
echo 'Connexion impossible : ' . htmlspecialchars($e->getMessage());
}PHPOn lit les identifiants avec getenv() : ils viennent des variables environment du compose.yaml, elles-mêmes alimentées par le .env. Aucun mot de passe n’est écrit dans le code.
Étape 7 : lancer l’environnement
docker compose up -d --buildBashupcrée et démarre les conteneurs ;-d(detached) les lance en arrière-plan ;--buildconstruit notre image PHP (seulement nécessaire la première fois ou après une modification du Dockerfile).
La première exécution télécharge les images (quelques minutes selon ta connexion). Ensuite, le démarrage prend quelques secondes. Vérifie que tout tourne :
docker compose psBashOuvre ensuite :
| Adresse | Service |
|---|---|
http://localhost:8080 | Ton application PHP |
http://localhost:8081 | phpMyAdmin (utilisateur app et son mot de passe) |
localhost:3307 | MySQL depuis un client comme DBeaver ou HeidiSQL |
Recharge la page de l’application : le compteur de visites augmente. Tes données sont bien enregistrées dans MySQL.
Les commandes Docker du quotidien
| Commande | Effet |
|---|---|
docker compose up -d | Démarre l’environnement |
docker compose stop | Arrête les conteneurs (sans les supprimer) |
docker compose down | Arrête et supprime les conteneurs (les volumes sont conservés) |
docker compose down -v | Supprime aussi les volumes : ⚠️ ta base de données est effacée |
docker compose logs -f web | Affiche les journaux du service web en continu |
docker compose exec web bash | Ouvre un terminal dans le conteneur PHP |
docker compose exec web composer install | Lance Composer dans le conteneur |
docker compose exec db mysql -u app -p boutique | Ouvre la console MySQL |
docker compose build --no-cache | Reconstruit l’image depuis zéro |
Grâce à la ligne COPY --from=composer:2 du Dockerfile, Composer est disponible dans le conteneur. docker compose exec web composer require vlucas/phpdotenv installe la librairie dans src/vendor/, sans que PHP ni Composer soient installés sur ta machine.
Sauvegarder et restaurer la base de données
# Sauvegarde : crée un fichier SQL sur ton poste
docker compose exec db sh -c 'mysqldump -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' > sauvegarde.sql
# Restauration
docker compose exec -T db sh -c 'mysql -u root -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE"' < sauvegarde.sqlBashLes variables $MYSQL_ROOT_PASSWORD et $MYSQL_DATABASE sont lues dans le conteneur (les guillemets simples empêchent ton terminal de les interpréter), donc le mot de passe n’apparaît pas dans ton historique de commandes.
Astuce : tout fichier .sql placé dans le dossier /docker-entrypoint-initdb.d du conteneur MySQL est exécuté automatiquement à la première création de la base. Ajoute la ligne - ./docker/mysql:/docker-entrypoint-initdb.d dans les volumes du service db pour créer tes tables et tes données de test automatiquement.
Adapter l’environnement à ton projet
Le modèle est facile à faire évoluer :
- Changer de version de PHP : remplace
php:8.4-apacheparphp:8.3-apachedans le Dockerfile, puisdocker compose up -d --build. Tu peux tester ton projet sur une nouvelle version de PHP en une minute, sans rien désinstaller. - Utiliser MariaDB : remplace
mysql:8.4parmariadb:11(les variables sont les mêmes, préfixéesMARIADB_dans les versions récentes ; les anciennesMYSQL_restent acceptées). - Framework avec dossier public (Laravel, Symfony) : change la racine d’Apache dans le Dockerfile :
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.confDockerfile- Capturer les e-mails : ajoute un service Mailpit pour voir tous les e-mails envoyés par ton application, sans qu’ils partent réellement.
mailpit:
image: axllent/mailpit
ports:
- "8025:8025" # interface webYAMLLe principe est exactement le même, avec l’image officielle wordpress. Notre guide WordPress en local avec Docker te donne le fichier compose complet.
Docker pour développeur web : les bonnes pratiques
Quelques règles qui distinguent un environnement Docker bricolé d’un environnement professionnel :
- Épingle les versions des images.
mysql:8.4plutôt quemysql:latest: sinon, une mise à jour de l’image peut changer ta version de MySQL du jour au lendemain, sans prévenir. - Un service par conteneur. PHP dans un conteneur, MySQL dans un autre, Mailpit dans un troisième. C’est ce qui permet de les mettre à jour, de les redémarrer et de les remplacer indépendamment.
- Les données dans des volumes nommés. Tout ce qui doit survivre (bases de données, fichiers envoyés par les utilisateurs) va dans un volume. Le conteneur, lui, doit pouvoir être supprimé et recréé à tout moment.
- Aucun secret dans les images. Les mots de passe passent par le fichier
.envou par les secrets de Docker, jamais par une ligneENVdu Dockerfile : ils seraient visibles par toute personne qui récupère l’image. - Des images légères. Supprime les caches des gestionnaires de paquets dans la même instruction
RUN, et ajoute un fichier.dockerignorepour ne pas copiernode_modules,.gitou les fichiers de logs dans l’image. - Nettoie régulièrement. Les images et volumes inutilisés s’accumulent vite.
docker system dfmontre l’espace occupé etdocker system prunesupprime ce qui ne sert plus (lis bien l’avertissement avant de valider).
# .dockerignore
.git
.env
node_modules
vendor
*.logTexteDépannage
| Problème | Cause | Solution |
|---|---|---|
port is already allocated | Le port 8080 (ou 3307) est déjà utilisé | Change le port de gauche : "8090:80" |
SQLSTATE[HY000] [2002] Connection refused | MySQL pas encore prêt | Le healthcheck et depends_on règlent ce cas ; sinon attends quelques secondes |
Access denied for user 'app' | Mots de passe modifiés après la première création | Les variables MySQL ne servent qu’à la première initialisation : docker compose down -v puis relance (⚠️ efface la base) |
could not find driver | Extension pdo_mysql absente | Vérifie le Dockerfile et relance avec --build |
| Très lent sous Windows | Projet stocké sur le disque Windows | Place ton projet dans le système de fichiers WSL (\\wsl$\Ubuntu\home\…) |
| Fichiers créés par le conteneur non modifiables (Linux) | Le conteneur écrit en tant que www-data ou root | Ajuste les droits ou configure l’utilisateur du conteneur |
Docker en développement, et en production ?
Cet environnement est conçu pour le développement : erreurs affichées, code partagé en direct, phpMyAdmin exposé. Pour la production, on construit une image différente : php.ini-production, code copié dans l’image (pas partagé), pas de phpMyAdmin, mots de passe gérés par des secrets, et souvent PHP-FPM derrière Nginx. Mais la logique reste la même, et c’est tout l’intérêt de Docker : ce que tu testes sur ton poste ressemble enfin à ce qui tourne en ligne.

FAQ
Docker remplace-t-il WampServer ou XAMPP ?
Oui, pour le développement. L’avantage : chaque projet a sa propre version de PHP et de MySQL, isolée des autres, et l’environnement est décrit dans un fichier partageable avec ton équipe. Si tu préfères une installation sans Docker, notre guide pour installer PHP 8.4 et Composer sous Windows est une excellente alternative.
Mes données MySQL sont-elles perdues quand j’arrête Docker ?
Non. Elles sont stockées dans le volume db_data, qui survit à docker compose stop et docker compose down. Seul docker compose down -v supprime les volumes.
Docker est-il gourmand en ressources ?
Beaucoup moins qu’une machine virtuelle. Cet environnement consomme quelques centaines de Mo de mémoire. Sous Windows, tu peux limiter la mémoire allouée à WSL 2 avec un fichier .wslconfig.
Faut-il commiter compose.yaml et le Dockerfile ?
Oui, absolument : ils font partie du projet et permettent à n’importe qui de le lancer avec docker compose up. Seul le fichier .env (qui contient les mots de passe) reste en dehors de Git.
Conclusion
Avec trois fichiers (Dockerfile, compose.yaml et .env), tu disposes d’un environnement de développement PHP et MySQL complet, reproductible et isolé. Docker pour développeur web, c’est la fin des « chez moi ça marche » : n’importe qui clone ton projet, tape docker compose up -d et obtient exactement le même environnement que toi.
Garde ce modèle sous la main : il te servira de base pour tous tes futurs projets. Pour versionner ces fichiers et les partager, notre guide Git et GitHub pour débutant t’explique comment. Et la documentation officielle de Docker Compose détaille toutes les options disponibles.

