Remplacer XAMPP par Docker : environnement LAMP multi-sites sur Windows

Mettre en place un stack LAMP avec Docker Compose sur Windows pour remplacer XAMPP ou WAMP. Comparatif perfs chiffré, guide pas-à-pas avec Apache, PHP 8.4, MySQL et PhpMyAdmin.

10 min de lectureDéveloppement Web Linux
Remplacer XAMPP par Docker : environnement LAMP multi-sites sur Windows

Tu galères à maintenir plusieurs projets PHP sur ta machine ? Entre XAMPP qui rame, les conflits de versions et le fameux “ça marche sur mon PC mais pas sur le serveur”, voilà pourquoi Docker est devenu mon setup de dev par défaut.

Ce guide montre comment mettre en place un serveur LAMP complet avec Docker Compose sur Windows : Apache, PHP 8.4, MySQL et PhpMyAdmin, capable d’héberger autant de projets que tu veux en parallèle.

Pourquoi remplacer XAMPP par Docker ?

Le problème avec XAMPP/WAMP

Quand on développe avec une stack PHP classique, on installe tout en dur sur sa machine. Le résultat, c’est prévisible :

  • “Ça marche sur mon PC mais pas sur le serveur” (versions différentes)
  • Conflits entre projets qui nécessitent des versions PHP différentes
  • Impossible de travailler proprement sur deux projets avec des configs distinctes
  • Désinstallation compliquée quand quelque chose foire

Ce que Docker change

Docker crée des conteneurs : des environnements isolés qui embarquent tout ce dont un projet a besoin. Pas d’installation sur ta machine, pas de conflits, tout reproductible en une commande.

Le chiffre qui parle : sur une même machine sous Ubuntu, la stack LAMP installée en dur consomme 45% du CPU et 4,6 Go de RAM au repos, dont les deux tiers pour l’interface graphique et les services inutiles. Avec Docker Compose, la même stack tourne à 12% du CPU et 900 Mo de RAM.

block-beta columns 4 TITLE1["AVANT - OS Ubuntu Desktop\nCPU 45% RAM 4.6Go sur 8Go"]:4 GUI["Interface Graphique GNOME\n\nCPU 20% RAM 2.5Go"]:4 STITLE["Services inutiles - CPU 5% RAM 500Mo"]:4 s1["snapd\n180Mo RAM"]:1 s2["CUPS\nimprimante"]:1 s3["Postfix\nmail"]:1 s4["Bluetooth"]:1 LTITLE["Stack LAMP installée en dur - CPU 11% RAM 800Mo"]:4 APACHE1["Apache 2.4\nCPU 3% RAM 150Mo"]:1 MYSQL1["MySQL 8.0\nCPU 5% RAM 400Mo"]:1 PHP1["PHP 8.1\nCPU 2% RAM 120Mo"]:1 PMA1["phpMyAdmin\n+ Certbot"]:1 classDef title fill:#4a4a6a,stroke:#4a4a6a,color:#fff,font-weight:bold classDef gui fill:#ff0040,stroke:#cc0033,color:#fff,font-weight:bold classDef stitle fill:#e74c3c,stroke:#c0392b,color:#fff,font-weight:bold classDef svc fill:#e6b0aa,stroke:#c0392b,color:#000 classDef ltitle fill:#607d8b,stroke:#455a64,color:#fff,font-weight:bold classDef apache fill:#d45800,stroke:#b34700,color:#fff classDef mysql fill:#0066cc,stroke:#004c99,color:#fff classDef php fill:#6a1b9a,stroke:#4a148c,color:#fff classDef pma fill:#c6a700,stroke:#a68b00,color:#000 class TITLE1 title class GUI gui class STITLE stitle class s1,s2,s3,s4 svc class LTITLE ltitle class APACHE1 apache class MYSQL1 mysql class PHP1 php class PMA1 pma
block-beta columns 4 TITLE2["APRES - Docker Compose\nCPU 12% RAM 905Mo sur 8Go"]:4 APACHE2["Apache 2.4-alpine\nCPU 3% RAM 80Mo"]:1 MYSQL2["MySQL 8.4\nCPU 5% RAM 350Mo"]:1 PHP2["PHP 8.3-fpm-alpine\nCPU 2% RAM 60Mo"]:1 PMA2["phpMyAdmin 5.2\n"]:1 ENGINE["Docker Engine\nCPU 1% RAM 60Mo"]:1 FREE["Ressources disponibles\nCPU 88% RAM 7Go"]:3 classDef title fill:#4a4a6a,stroke:#4a4a6a,color:#fff,font-weight:bold classDef apache fill:#d45800,stroke:#b34700,color:#fff classDef mysql fill:#0066cc,stroke:#004c99,color:#fff classDef php fill:#6a1b9a,stroke:#4a148c,color:#fff classDef pma fill:#c6a700,stroke:#a68b00,color:#000 classDef engine fill:#78909c,stroke:#546e7a,color:#fff classDef free fill:#2ecc71,stroke:#27ae60,color:#fff,font-weight:bold class TITLE2 title class APACHE2 apache class MYSQL2 mysql class PHP2 php class PMA2 pma class ENGINE engine class FREE free

Et surtout : un docker compose up reproduit le même environnement sur n’importe quelle machine, dev comme prod.

Les concepts Docker à connaître avant de commencer

Quelques définitions rapides pour que la suite soit claire :

Image : un modèle en lecture seule. Pense à une recette de cuisine : elle décrit ce qu’il faut, mais ne produit rien tant qu’on ne l’exécute pas.

Conteneur : une instance en cours d’exécution d’une image. Le plat que tu as cuisiné à partir de la recette. Tu peux en faire plusieurs à partir de la même image.

Volume : un espace de stockage persistant. Quand tu supprimes un conteneur, ses données disparaissent - sauf celles dans un volume. C’est le disque dur externe du conteneur.

Dockerfile : un fichier texte avec les instructions pour construire une image personnalisée.

docker-compose.yml : le fichier qui orchestre plusieurs conteneurs en même temps. La carte du restaurant avec tous les plats qui doivent être servis ensemble.

flowchart TD subgraph CONFIG["Configuration"] DC["docker-compose.yml"] DF["Dockerfile.php"] end subgraph IMAGES["Images"] IMG_PHP["Image PHP/Apache (personnalisée)"] IMG_MYSQL["Image MySQL (officielle)"] IMG_PMA["Image PhpMyAdmin (officielle)"] end subgraph SERVICES["Services (conteneurs)"] C_PHP["PHP/Apache :80"] C_MYSQL["MySQL :3306"] C_PMA["PhpMyAdmin"] end subgraph STOCKAGE["Stockage"] VOL[("Volume mysql_data")] end DC -->|référence| DF DF -->|produit| IMG_PHP DC -->|définit| IMG_MYSQL DC -->|définit| IMG_PMA IMG_PHP --> C_PHP IMG_MYSQL --> C_MYSQL IMG_PMA --> C_PMA C_MYSQL --> VOL C_PMA --> C_MYSQL

Structure du projet

Voici l’arborescence à créer. On va remplir chaque fichier dans les étapes suivantes :

C:\Dev\PHP\
  |-- .env                    # Variables d'environnement (mots de passe, versions)
  |-- .gitignore              # Fichiers ignorés par Git
  |-- Dockerfile.php          # Instructions pour construire l'image PHP/Apache
  |-- docker-compose.yml      # Orchestration de tous les services
  |-- apache-config/
  |   |-- localhost.conf      # Configuration du site principal
  |-- www/                    # Tes fichiers sources (synchronisés avec le conteneur)
  |   |-- index.php           # Page d'accueil (ou dashboard)
  |   |-- info.php            # phpinfo()
Information
Le dossier www/ est synchronisé avec le conteneur Apache via un bind mount. Tout fichier que tu y places est immédiatement accessible dans le navigateur, sans relancer Docker.

Règles à retenir dès le départ

  • Docker Desktop doit être lancé avant toute commande docker
  • Toutes les commandes docker compose s’exécutent depuis le dossier racine du projet
  • Le fichier .env ne doit jamais être versionné (il contient les mots de passe)
  • Après modification du Dockerfile, reconstruire avec docker compose up -d --build
  • Les données MySQL persistent dans un volume : supprimer un conteneur ne supprime pas les données

Étape 1 : Installer Docker Desktop

  1. Télécharge Docker Desktop depuis docker.com
  2. Installe-le et active WSL 2 quand c’est proposé (ou via PowerShell en admin : wsl --install)
  3. Redémarre, puis vérifie :
docker --version
# Docker version 29.x.x
Attention
Docker Desktop doit être lancé (icône dans la barre des tâches) pour que les commandes Docker fonctionnent.

Étape 2 : Le fichier .env

Le .env centralise toutes les variables de configuration utilisées par docker-compose.yml. Crée-le à la racine du projet :

# MySQL
MYSQL_ROOT_PASSWORD=root
MYSQL_DATABASE=test
MYSQL_USER=app_user
MYSQL_PASSWORD=app_password

# Connexion PHP vers MySQL
DB_HOST=mysql-server
DB_DATABASE=test
DB_USER=root
DB_PASS=root

# PhpMyAdmin
PMA_VERSION=5.2.2
PMA_HOST=mysql-server
PMA_USER=root
PMA_PASSWORD=root
Information
Pourquoi DB_HOST=mysql-server et pas localhost ? Dans Docker, les conteneurs communiquent par le nom du service défini dans docker-compose.yml. Le service MySQL s’appelle mysql-server, c’est donc ce nom qu’on utilise.

Étape 3 : Le Dockerfile

Il n’existe pas d’image Docker officielle qui contient exactement notre stack (PHP + Apache + extensions + Composer + Xdebug). On construit la nôtre à partir de l’image officielle PHP.

Crée le fichier Dockerfile.php à la racine :

# Image de base : PHP 8.4 avec Apache intégré
FROM php:8.4-apache

# Paquets système nécessaires pour compiler les extensions PHP
RUN apt-get update && apt-get install -y --no-install-recommends \
    libzip-dev libpq-dev libicu-dev libonig-dev \
    zip unzip curl git \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

# Extensions PHP
RUN docker-php-ext-install \
    pdo pdo_mysql pdo_pgsql mysqli zip \
    intl mbstring opcache

# Xdebug + APCu
RUN pecl install xdebug apcu \
  && docker-php-ext-enable xdebug apcu

# Composer (gestionnaire de dépendances PHP, équivalent de npm)
RUN curl -sS https://getcomposer.org/installer | \
    php -- --install-dir=/usr/local/bin --filename=composer

# Modules Apache
RUN a2enmod rewrite proxy proxy_http headers

# Configuration Apache personnalisée
COPY ./apache-config /etc/apache2/sites-enabled

WORKDIR /var/www/html

Extensions installées et leur rôle :

ExtensionRôle
pdo + pdo_mysqlConnexion BDD (couche abstraite)
mysqliConnexion MySQL classique (PhpMyAdmin)
zipManipulation d’archives (Composer)
intl + mbstringInternationalisation et UTF-8 (Symfony)
opcacheCache bytecode PHP (performances)
xdebugDébogage pas-à-pas
apcuCache utilisateur en mémoire
Attention
Le Dockerfile est exécuté une seule fois lors de la construction de l’image. Si tu le modifies, relance avec docker compose up -d --build.

Étape 4 : Le docker-compose.yml

C’est le fichier principal qui orchestre les 3 conteneurs. Crée docker-compose.yml à la racine :

services:
  # Base de données MySQL
  mysql-server:
    image: mysql:8.4
    container_name: mysql-db
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 3
    restart: "no"

  # Serveur web Apache + PHP
  php-apache-server:
    build:
      context: .
      dockerfile: Dockerfile.php
    container_name: php-apache
    volumes:
      - ./www:/var/www/html
      - ./apache-config:/etc/apache2/sites-enabled
    ports:
      - "80:80"
    environment:
      DB_HOST: ${DB_HOST}
      DB_DATABASE: ${DB_DATABASE}
      DB_USER: ${DB_USER}
      DB_PASS: ${DB_PASS}
      PHPMYADMIN_HOST: phpmyadmin
    depends_on:
      mysql-server:
        condition: service_healthy
      phpmyadmin:
        condition: service_started
    mem_limit: 512m
    memswap_limit: 512m
    restart: "no"

  # PhpMyAdmin
  phpmyadmin:
    image: phpmyadmin:${PMA_VERSION}
    container_name: phpmyadmin
    environment:
      PMA_HOST: ${PMA_HOST}
      PMA_USER: ${PMA_USER}
      PMA_PASSWORD: ${PMA_PASSWORD}
      PMA_ABSOLUTE_URI: http://localhost/phpmyadmin/
    depends_on:
      mysql-server:
        condition: service_healthy
    restart: "no"

volumes:
  mysql_data:

Quelques points importants :

depends_on + healthcheck : Apache ne démarre que quand MySQL est réellement prêt à accepter des connexions, pas juste démarré.

mem_limit: 512m : limite la RAM du conteneur PHP à 512 Mo pour éviter qu’un script mal écrit ne bouffe toute ta machine.

restart: "no" : les conteneurs ne démarrent pas automatiquement avec Docker Desktop. Tu gardes le contrôle.

PhpMyAdmin via proxy : au lieu d’exposer PhpMyAdmin sur localhost:8081, il est accessible via http://localhost/phpmyadmin/ grâce au proxy Apache. Plus propre.

Étape 5 : Configuration Apache

Crée le fichier apache-config/localhost.conf :

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot /var/www/html

    # Proxy PhpMyAdmin
    ProxyPreserveHost On
    ProxyRequests Off
    RedirectMatch ^/phpmyadmin$ /phpmyadmin/
    ProxyPass /phpmyadmin/ http://phpmyadmin:80/
    ProxyPassReverse /phpmyadmin/ http://phpmyadmin:80/

    <Location "/phpmyadmin/">
        ProxyPassReverse /
        SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
    </Location>

    <Directory /var/www/html>
        AllowOverride All
        Require all granted
        DirectoryIndex index.php index.html
    </Directory>

    <FilesMatch "\.php$">
        SetHandler application/x-httpd-php
    </FilesMatch>

    ErrorLog /var/log/apache2/error.log
    CustomLog /var/log/apache2/access.log combined
</VirtualHost>

Étape 6 : Lancer l’environnement

Ouvre un terminal dans C:\Dev\PHP (WSL, PowerShell ou CMD) et lance :

docker compose up -d --build

Docker va :

  1. Lire le .env et charger les variables
  2. Télécharger les images MySQL et PhpMyAdmin (première fois uniquement)
  3. Construire l’image PHP/Apache depuis le Dockerfile
  4. Créer le réseau interne entre les conteneurs
  5. Démarrer MySQL, attendre qu’il soit prêt (healthcheck)
  6. Démarrer PhpMyAdmin et PHP/Apache
# Vérifier que les 3 conteneurs tournent
docker compose ps
NAME          STATUS             PORTS
mysql-db      Up (healthy)       0.0.0.0:3306->3306/tcp
php-apache    Up                 0.0.0.0:80->80/tcp
phpmyadmin    Up                 80/tcp

Accès :

  • http://localhost : tes fichiers PHP dans www/
  • http://localhost/phpmyadmin/ : interface MySQL
  • http://localhost/info.php : page phpinfo() (si le fichier existe)

Les commandes Docker du quotidien

Démarrer et arrêter

docker compose up -d              # Démarrer les conteneurs
docker compose up -d --build      # Démarrer + reconstruire les images
docker compose stop               # Arrêter (conserve les conteneurs)
docker compose start              # Relancer des conteneurs arrêtés
docker compose down               # Arrêter ET supprimer les conteneurs (données préservées)

Observer et déboguer

docker compose ps                              # Statut des conteneurs
docker compose logs                            # Logs de tous les services
docker compose logs php-apache-server          # Logs d'un service spécifique
docker compose logs -f                         # Logs en temps réel (Ctrl+C pour quitter)

Entrer dans un conteneur

Pour utiliser Composer ou exécuter des commandes PHP directement :

docker compose exec php-apache-server bash

# À l'intérieur du conteneur :
php -v
composer --version
cd /var/www/html/mon-projet && composer require vlucas/phpdotenv
exit

Gestion des données

docker volume ls                     # Lister les volumes
docker compose down                  # Supprime les conteneurs, garde les données
docker compose down --volumes        # Supprime conteneurs ET données (irréversible)
Attention
docker compose down --volumes supprime définitivement les bases de données MySQL. Ne l’utilise que si tu veux repartir de zéro.

Gérer plusieurs projets (VirtualHosts)

Par défaut, tout dossier créé dans www/ est accessible via http://localhost/nom-du-dossier/ sans aucune config. Pour les projets Symfony ou Laravel qui nécessitent un DocumentRoot propre, il faut un VirtualHost dédié.

1. Crée le dossier du projet dans www/

2. Crée un fichier de config Apache dans apache-config/mon-projet.conf :

<VirtualHost *:80>
    ServerName mon-projet.local
    DocumentRoot /var/www/html/mon-projet
    <Directory /var/www/html/mon-projet>
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

3. Modifie le fichier hosts Windows (Bloc-notes en administrateur, C:\Windows\System32\drivers\etc\hosts) :

127.0.0.1 mon-projet.local

4. Relance Apache :

docker compose up -d
Information
Pour les projets simples, localhost/mon-projet/ suffit, aucune config nécessaire. Le VirtualHost dédié sert surtout pour Symfony/Laravel qui ont besoin d’un DocumentRoot à la racine du projet.

Dépannage : problèmes courants

“docker” n’est pas reconnu : Docker Desktop n’est pas lancé. Attends l’icône verte dans la barre des tâches.

MySQL ne démarre pas : conflit sur le port 3306, souvent WAMP/XAMPP encore actif. Arrête-le ou change le port dans docker-compose.yml : "3307:3306".

Erreur PhpMyAdmin “Error in processing request” : vérifie que PMA_ABSOLUTE_URI: http://localhost/phpmyadmin/ est bien présent dans le service phpmyadmin.

Conflit sur le port 80 : un autre serveur web tourne (IIS, WAMP, Skype). Arrête-le ou change le port : "8080:80".

VirtualHost qui ne répond pas : vérifie le fichier hosts (ouvert en administrateur ?), vide le cache DNS avec ipconfig /flushdns.

Les modifs PHP ne s’affichent pas : cache navigateur, force le rafraîchissement avec Ctrl + Shift + R.

Pour aller plus loin

Sauvegarder et restaurer MySQL

# Sauvegarder
docker compose exec mysql-server mysqldump -u root -proot --all-databases > backup.sql

# Restaurer
docker compose exec -T mysql-server mysql -u root -proot < backup.sql

Mettre à jour les versions

Les versions sont centralisées dans .env et docker-compose.yml :

ComposantOù modifierExemple
PHPDockerfile.php (ligne FROM)php:8.4-apache
MySQLdocker-compose.yml (image)mysql:8.4
PhpMyAdmin.env (PMA_VERSION)5.2.2

Après modification : docker compose up -d --build

Attention
Pour les mises à jour majeures de MySQL (ex : 8.x vers 9.x), fais toujours une sauvegarde avant. Les mises à jour mineures sont généralement sans risque car les données persistent dans le volume.

Sources et références :

Commentaires

Partager cet article

Articles connexes