Si vous avez déjà perdu une demi-journée parce que “ça marche sur ma machine” mais pas sur celle d’un collègue, Docker est souvent le point de bascule. Le vrai gain n’est pas “d’avoir WordPress dans un conteneur”, c’est de figer toutes les dépendances (PHP, extensions, base de données, Redis, mail, outils) et de pouvoir reconstruire l’environnement à l’identique.
Ce qu’on va construire
Vous allez mettre en place un environnement de développement WordPress reproductible, pensé pour WordPress 6.9.4 (avril 2026) et PHP 8.3+ (PHP minimum recommandé : 8.1). L’objectif est d’obtenir une stack “proche prod” mais orientée dev : Xdebug, WP-CLI, logs lisibles, capture des e-mails, cache Redis optionnel, et un bootstrap automatisé.
Ce setup est adapté à :
- Sites vitrine et blogs avec thème sur mesure (ou thème enfant),
- Sites builder (Divi 5, Elementor, Avada) où vous voulez isoler les plugins et les versions,
- Agences qui doivent onboarder vite un nouveau dev ou reproduire un bug client.
À la fin, vous saurez :
- lancer WordPress 6.9.4 en une commande,
- installer automatiquement WP, plugins et données de base,
- debugger proprement (Xdebug + logs),
- capturer les e-mails sortants sans toucher à un SMTP réel,
- versionner la config sans versionner les secrets.
Résumé rapide
- On part d’un projet Git avec
.env(non versionné) +.env.example(versionné). - On utilise docker compose avec services :
wp,db,redis,mailpit,traefik(option). - On construit une image
wp-devbasée surwordpress:php8.3-apache+ Xdebug + WP-CLI. - On automatise l’installation via WP-CLI (idempotent) et on garde les uploads en volume.
- On force une stratégie logs + debug stable :
WP_DEBUG_LOG, logs Apache, Xdebug en “trigger”.
Quand utiliser cette solution
Je la recommande quand vous avez au moins un de ces besoins :
- Reproduire un bug client : vous figez versions PHP/extensions, MySQL/MariaDB, et vous évitez les “diffs invisibles”.
- Travailler en équipe : même stack pour tout le monde, y compris CI.
- Maintenir plusieurs projets : chaque projet embarque sa stack, sans polluer votre machine.
- Tester des plugins lourds (builders, WooCommerce) sans casser votre environnement local.
Dans mon expérience, c’est particulièrement utile sur des sites Elementor/Avada où une extension PHP manquante (intl, zip, gd) fait “juste” planter un import de template, sans message exploitable.
Quand ne PAS utiliser cette solution
- Si vous êtes sur une machine très limitée (RAM/CPU) et que votre projet est minuscule : Docker peut être plus lent qu’un PHP local.
- Si vous faites uniquement du contenu (pas de dev) : un staging managé est souvent plus simple.
- Si votre équipe n’a pas la discipline de versionner correctement
.env.exampleet les scripts : vous allez créer un “Docker qui marche chez une seule personne”, ce qui est pire qu’avant.
Avant de commencer (prérequis)
Préparez-vous comme si vous alliez casser quelque chose, parce que vous allez casser quelque chose.
Prérequis techniques
- Docker Desktop (macOS/Windows) ou Docker Engine (Linux) + plugin Compose. Docs : Docker Compose.
- Git.
- Un éditeur qui gère bien les fins de ligne (VS Code par exemple).
Versions ciblées
- WordPress : 6.9.4 (vous pouvez changer la version plus tard, mais on part sur celle-ci).
- PHP : 8.3 dans l’image (compatible WP 6.9.4, et au-dessus du minimum 8.1).
- MariaDB : 10.11 LTS (bon compromis stabilité/compat).
Sauvegarde & environnement
- Ne testez pas ça sur un site en production. Faites-le dans un dépôt dédié.
- Si vous migrez un site existant : export DB +
wp-content/uploadsavant de commencer.
Sécurité (local mais pas “sans risque”)
- Ne commitez jamais
.env(mots de passe DB, salts, clés). - Évitez d’exposer MySQL/Redis sur
0.0.0.0si ce n’est pas nécessaire.
Références officielles utiles :
- wp-config.php (WordPress Developer Resources)
- WP-CLI Commands
- Debugging in WordPress
- PHP Opcache (php.net)
Étape 1 : Créer une structure de projet propre et versionnable
Objectif : séparer ce qui est versionné (config, scripts) de ce qui ne l’est pas (secrets, volumes, uploads).
1) Créez l’arborescence
Dans un dossier vide :
mkdir -p wp-docker/{docker,bin,wp,wp-content,logs}
cd wp-docker
Vous allez obtenir :
docker/: Dockerfile, conf Apache/PHP, scripts d’entréebin/: scripts utilitaires (install, reset, import db…)wp/: WordPress core (monté en volume) ou docroot selon variantewp-content/: thèmes/plugins mu-plugins (optionnel) versionnéslogs/: logs persistants
2) Ajoutez un .gitignore strict
Créez .gitignore à la racine :
cat > .gitignore <<'EOF'
# Secrets
.env
# Volumes / données locales
data/
logs/*.log
wp/wp-config.php
# Dépendances éventuelles
node_modules/
vendor/
# OS / IDE
.DS_Store
.idea/
.vscode/
EOF
3) Créez vos fichiers d’environnement
Créez .env.example (versionné) :
cat > .env.example <<'EOF'
# Domaine local (utilisé par WP_HOME/WP_SITEURL)
WP_HOST=wp.local
# WordPress
WP_VERSION=6.9.4
WP_ENV=development
# Base de données
DB_NAME=wordpress
DB_USER=wordpress
DB_PASSWORD=wordpress
DB_ROOT_PASSWORD=root
DB_HOST=db
# Ports
HTTP_PORT=8080
MAILPIT_PORT=8025
# Xdebug
XDEBUG_MODE=debug,develop
XDEBUG_TRIGGER=1
EOF
Puis copiez-le en .env (non versionné) :
cp .env.example .env
Résultat attendu
Vous avez un dépôt prêt à accueillir docker-compose.yml sans secrets committés. C’est le point où beaucoup se trompent : j’ai souvent vu des mots de passe DB dans Git “parce que c’est du local”. Le jour où un repo devient public par erreur, c’est trop tard.
Étape 2 : Écrire un docker-compose reproductible (PHP 8.3+, MariaDB, Redis)
Objectif : déclarer une stack stable, avec des volumes nommés, et des ports explicites.
1) Créez docker-compose.yml
À la racine, créez docker-compose.yml :
cat > docker-compose.yml <<'EOF'
services:
wp:
build:
context: .
dockerfile: docker/Dockerfile
container_name: wp_app
env_file: .env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
mailpit:
condition: service_started
ports:
- "${HTTP_PORT:-8080}:80"
volumes:
# Code WordPress (core) + contenu
- ./wp:/var/www/html
- ./wp-content:/var/www/html/wp-content
# Logs persistants
- ./logs:/var/log/wp
extra_hosts:
# Permet à Xdebug de joindre l'hôte sur Linux (optionnel sur Mac/Windows)
- "host.docker.internal:host-gateway"
db:
image: mariadb:10.11
container_name: wp_db
environment:
MYSQL_DATABASE: ${DB_NAME:-wordpress}
MYSQL_USER: ${DB_USER:-wordpress}
MYSQL_PASSWORD: ${DB_PASSWORD:-wordpress}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "127.0.0.1", "-uroot", "-p${DB_ROOT_PASSWORD}"]
interval: 5s
timeout: 3s
retries: 30
redis:
image: redis:7-alpine
container_name: wp_redis
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
mailpit:
image: axllent/mailpit:latest
container_name: wp_mailpit
ports:
- "${MAILPIT_PORT:-8025}:8025"
volumes:
db_data:
redis_data:
EOF
2) Pourquoi ces choix
- Volumes nommés (
db_data,redis_data) : vous évitez de versionner des données et vous pouvez reset facilement. - Healthcheck DB : sans ça, WP-CLI peut tenter l’install avant que MariaDB ne soit prête (race condition classique).
- Mailpit : vous voyez les mails sortants, sans SMTP. Très pratique pour les formulaires.
Résultat attendu
À ce stade, docker compose up échouera encore car l’image wp n’existe pas. C’est normal : on la construit à l’étape suivante.
Étape 3 : Construire une image WordPress “dev” (Xdebug, WP-CLI, extensions)
Objectif : ne pas dépendre d’un conteneur “wordpress vanilla” dès que vous avez besoin d’outils (WP-CLI, Xdebug, zip, intl…). Je préfère une image dédiée, car sinon vous finissez par bricoler à la main dans le conteneur, et ce n’est plus reproductible.
1) Créez docker/Dockerfile
cat > docker/Dockerfile <<'EOF'
FROM wordpress:php8.3-apache
# Paquets système utiles en dev (zip, intl, mysql client, etc.)
RUN apt-get update && apt-get install -y --no-install-recommends
git
unzip
less
mariadb-client
libzip-dev
libicu-dev
&& docker-php-ext-install zip intl
&& rm -rf /var/lib/apt/lists/*
# Activer mod_rewrite (permalinks)
RUN a2enmod rewrite headers
# Xdebug (install via PECL)
RUN pecl install xdebug
&& docker-php-ext-enable xdebug
# WP-CLI (binaire officiel)
RUN curl -sSLo /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
&& chmod +x /usr/local/bin/wp
&& wp --info
# Configuration PHP (logs, limites)
COPY docker/php.ini /usr/local/etc/php/conf.d/99-wp-dev.ini
# Configuration Apache (DocumentRoot, logs)
COPY docker/apache.conf /etc/apache2/sites-available/000-default.conf
# Script d'entrée (bootstrap idempotent)
COPY docker/entrypoint.sh /usr/local/bin/wp-entrypoint
RUN chmod +x /usr/local/bin/wp-entrypoint
ENTRYPOINT ["wp-entrypoint"]
CMD ["apache2-foreground"]
EOF
2) Ajoutez docker/php.ini
cat > docker/php.ini <<'EOF'
; Logs PHP vers stderr + fichier (pratique en debug)
log_errors = On
error_reporting = E_ALL
display_errors = Off
; Ajustez selon vos besoins
memory_limit = 512M
upload_max_filesize = 128M
post_max_size = 128M
max_execution_time = 120
; Xdebug en mode "trigger" pour éviter de ralentir tout le monde
xdebug.mode = ${XDEBUG_MODE}
xdebug.start_with_request = trigger
xdebug.trigger_value = ${XDEBUG_TRIGGER}
; Sur Mac/Windows: host.docker.internal marche.
; Sur Linux: extra_hosts dans compose.
xdebug.client_host = host.docker.internal
xdebug.client_port = 9003
xdebug.log_level = 0
EOF
3) Ajoutez docker/apache.conf
cat > docker/apache.conf <<'EOF'
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
<Directory /var/www/html>
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/log/wp/apache-error.log
CustomLog /var/log/wp/apache-access.log combined
</VirtualHost>
EOF
4) Ajoutez docker/entrypoint.sh
Ce script fait trois choses : attendre la DB, télécharger WP si absent, générer un wp-config.php propre (avec salts et debug).
cat > docker/entrypoint.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Petit helper de logs
log() {
echo "[wp-entrypoint] $*"
}
# Attendre MariaDB (évite la course au démarrage)
wait_for_db() {
log "Attente de la base de données ${DB_HOST:-db}..."
for i in {1..60}; do
if mariadb-admin ping -h"${DB_HOST:-db}" -u"${DB_USER:-wordpress}" -p"${DB_PASSWORD:-wordpress}" --silent; then
log "Base de données prête."
return 0
fi
sleep 2
done
log "ERREUR: la base n'est pas prête après 120s."
return 1
}
# Télécharger WordPress si /var/www/html est vide (ou sans wp-includes)
ensure_wordpress_core() {
if [ ! -d /var/www/html/wp-includes ]; then
log "WordPress core absent, téléchargement de WP ${WP_VERSION:-6.9.4}..."
wp core download --version="${WP_VERSION:-6.9.4}" --path=/var/www/html --allow-root
else
log "WordPress core déjà présent."
fi
}
# Générer wp-config.php si absent
ensure_wp_config() {
if [ ! -f /var/www/html/wp-config.php ]; then
log "Génération de wp-config.php..."
wp config create
--path=/var/www/html
--dbname="${DB_NAME:-wordpress}"
--dbuser="${DB_USER:-wordpress}"
--dbpass="${DB_PASSWORD:-wordpress}"
--dbhost="${DB_HOST:-db}"
--skip-check
--allow-root
# Ajouter des constantes utiles en dev (et Redis en option)
wp config set WP_ENVIRONMENT_TYPE "${WP_ENV:-development}" --type=constant --allow-root
wp config set WP_DEBUG true --type=constant --allow-root
wp config set WP_DEBUG_LOG true --type=constant --allow-root
wp config set WP_DEBUG_DISPLAY false --type=constant --allow-root
wp config set SCRIPT_DEBUG true --type=constant --allow-root
# Logs WordPress vers un fichier monté
wp config set WP_CONTENT_DIR "/var/www/html/wp-content" --type=constant --allow-root
wp config set WP_CONTENT_URL "http://localhost/wp-content" --type=constant --allow-root
# Redis (si plugin présent plus tard)
wp config set WP_REDIS_HOST "redis" --type=constant --allow-root
wp config set WP_REDIS_PORT 6379 --type=constant --allow-root
else
log "wp-config.php déjà présent."
fi
}
wait_for_db
ensure_wordpress_core
ensure_wp_config
exec "$@"
EOF
Résultat attendu
L’image est prête. Vous avez un point de bootstrap unique, versionnable, et vous évitez le piège classique “j’ai installé WP-CLI dans mon conteneur à la main, mais personne d’autre ne l’a”.
Étape 4 : Installer WordPress 6.9.4 et figer la configuration
Objectif : démarrer les conteneurs, puis faire l’installation WordPress de manière contrôlée (et reproductible).
1) Démarrez la stack
docker compose up -d --build
Vérifiez :
- WordPress : http://localhost:8080
- Mailpit : http://localhost:8025
2) Installez WordPress via WP-CLI
Exécutez :
docker compose exec wp wp core install
--url="http://localhost:8080"
--title="WP Docker Dev"
--admin_user="admin"
--admin_password="admin"
--admin_email="[email protected]"
--skip-email
--allow-root
Oui, le mot de passe est volontairement faible : on est en local. Ne copiez pas ce pattern en staging public.
3) Réglez les permaliens
Sans ça, vous allez croire que mod_rewrite est cassé :
docker compose exec wp wp rewrite structure '/%postname%/' --hard --allow-root
Résultat attendu
Vous pouvez vous connecter à /wp-admin, créer un article, et les URLs “jolies” fonctionnent.
Étape 5 : Automatiser l’installation et les données (WP-CLI, scripts)
Objectif : une commande = environnement prêt. C’est ici que la reproductibilité devient réelle.
1) Créez un script bin/install.sh
cat > bin/install.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
# Script d'installation idempotent pour WordPress en Docker
# Usage: ./bin/install.sh
HTTP_PORT="${HTTP_PORT:-8080}"
docker compose up -d --build
# Attendre que WP réponde (évite les installs trop rapides)
echo "[install] Attente HTTP..."
for i in {1..60}; do
if curl -sSf "http://localhost:${HTTP_PORT}/wp-login.php" > /dev/null; then
break
fi
sleep 2
done
# Installer WP si pas déjà installé
if docker compose exec -T wp wp core is-installed --allow-root > /dev/null 2>&1; then
echo "[install] WordPress déjà installé."
else
echo "[install] Installation WordPress..."
docker compose exec -T wp wp core install
--url="http://localhost:${HTTP_PORT}"
--title="WP Docker Dev"
--admin_user="admin"
--admin_password="admin"
--admin_email="[email protected]"
--skip-email
--allow-root
docker compose exec -T wp wp rewrite structure '/%postname%/' --hard --allow-root
fi
# Installer des plugins utiles en dev
echo "[install] Installation plugins dev..."
docker compose exec -T wp wp plugin install query-monitor --activate --allow-root
# Redis cache (optionnel) : on l'installe mais on ne l'active pas si vous ne voulez pas
docker compose exec -T wp wp plugin install redis-cache --allow-root || true
# Réglages de base
docker compose exec -T wp wp option update blogdescription "Environnement reproductible Docker" --allow-root
echo "[install] OK. WP: http://localhost:${HTTP_PORT} Admin: admin/admin"
EOF
chmod +x bin/install.sh
2) Lancez le bootstrap
./bin/install.sh
3) Pourquoi c’est mieux qu’un README
Un README ment toujours à un moment. Un script, lui, casse tout de suite si une étape manque. Et quand ça casse, vous savez où.
Résultat attendu
Vous pouvez supprimer vos volumes, relancer ./bin/install.sh, et retrouver un WP fonctionnel, avec Query Monitor activé.
Étape 6 : Debug & logs (WP_DEBUG, Xdebug, Query Monitor, error_log)
Objectif : rendre les bugs visibles, sans ralentir toute la stack.
1) Où lire les logs
- Logs Apache :
logs/apache-error.logetlogs/apache-access.log - Log WordPress : par défaut
wp-content/debug.log(carWP_DEBUG_LOGest àtrue)
Commande pratique :
tail -f logs/apache-error.log wp-content/debug.log
2) Xdebug sans tout ralentir
Le setup utilise xdebug.start_with_request=trigger. Concrètement :
- Sans trigger : Xdebug ne s’active pas, perf correcte.
- Avec trigger : vous activez le debug uniquement quand vous en avez besoin.
Exemple : ajoutez un paramètre ?XDEBUG_TRIGGER=1 à une URL, ou configurez votre IDE pour envoyer le cookie/trigger. Référence : Xdebug Step Debug.
3) Erreur réaliste : “Fatal error … call to undefined function”
Je vois souvent ça quand un snippet est collé dans le mauvais fichier (ou exécuté trop tôt). Dans WordPress, certains hooks n’existent pas “tout de suite”. Si vous testez un plugin mu-plugin, assurez-vous d’accrocher votre code à un hook approprié.
Mini mu-plugin de test (à versionner) : créez wp-content/mu-plugins/dev-tools.php :
<?php
/**
* Plugin Name: Dev Tools (local)
* Description: Aides au debug en environnement Docker.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'init', function () {
// Exemple : log contrôlé (évitez var_dump en prod)
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
error_log( '[dev-tools] init OK, WP ' . get_bloginfo( 'version' ) );
}
}, 20 );
Résultat : une ligne apparaît dans wp-content/debug.log à chaque requête.
Étape 7 : Capturer les e-mails et fiabiliser le cron (Mailpit + cron système)
Objectif : arrêter de “tester” des e-mails en envoyant de vrais messages, et éviter les surprises liées à WP-Cron.
1) Capturer les e-mails
Mailpit capture ce qui sort via mail(). Beaucoup de plugins utilisent PHPMailer via WordPress, qui finit souvent en sendmail dans un conteneur. Selon les images, ça peut varier.
Approche fiable : installer un plugin SMTP local pointant vers Mailpit (ou configurer PHPMailer via hook). Pour rester reproductible et sans UI, je préfère un mu-plugin.
Créez wp-content/mu-plugins/mailpit.php :
<?php
/**
* Plugin Name: Mailpit SMTP (local)
* Description: Force l'envoi SMTP vers Mailpit en dev.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'phpmailer_init', function ( $phpmailer ) {
// En local uniquement : si vous mettez ça en prod, vous allez perdre vos e-mails.
if ( defined( 'WP_ENVIRONMENT_TYPE' ) && WP_ENVIRONMENT_TYPE !== 'development' ) {
return;
}
$phpmailer->isSMTP();
$phpmailer->Host = 'mailpit';
$phpmailer->Port = 1025;
$phpmailer->SMTPAuth = false;
$phpmailer->SMTPSecure = '';
}, 10, 1 );
Test :
docker compose exec wp wp eval "wp_mail('[email protected]','Test','Hello');" --allow-root
Ouvrez Mailpit : http://localhost:8025.
2) Remplacer WP-Cron par un cron système (en dev)
WP-Cron dépend du trafic. En dev, c’est aléatoire. Je remplace souvent WP-Cron par un cron toutes les minutes.
Ajoutez dans wp-config.php (déjà généré) :
docker compose exec wp wp config set DISABLE_WP_CRON true --type=constant --allow-root
Puis ajoutez un service cron dans Compose (option avancée). Éditez docker-compose.yml et ajoutez :
cat >> docker-compose.yml <<'EOF'
cron:
image: curlimages/curl:8.7.1
container_name: wp_cron
depends_on:
- wp
command: >
sh -lc "while true; do
curl -sS http://wp/wp-cron.php?doing_wp_cron=1 > /dev/null || true;
sleep 60;
done"
EOF
Redémarrez :
docker compose up -d
Résultat : vos tâches planifiées (imports, sync, newsletters en dev) se déclenchent de manière stable.
Étape 8 : Ajouter des tests rapides (PHPUnit + wp-env en option)
Objectif : avoir un filet de sécurité minimal. Même sur un blog, un plugin maison ou un thème sur mesure mérite au moins quelques tests.
Option A : PHPUnit dans le conteneur (simple, rapide)
Vous pouvez installer PHPUnit via Composer dans votre thème/plugin. Si votre projet a déjà Composer, c’est le bon moment. Sinon, vous pouvez limiter à des tests manuels.
Exemple (plugin custom) : structurez un plugin wp-content/plugins/my-plugin et ajoutez Composer. Je ne détaille pas toute la mise en place ici, car ça dépend fortement de votre architecture, mais retenez ceci : gardez les tests hors du runtime WordPress.
Option B : wp-env (outil officiel dev core) pour tests isolés
@wordpress/env (wp-env) est pratique pour lancer un WP jetable pour tests, sans impacter votre stack Docker principale. C’est utile en CI.
Doc officielle : @wordpress/env.
Le résultat complet
Si vous voulez tout copier d’un coup, voici les fichiers clés assemblés. Personnalisez ensuite via .env et les mu-plugins.
docker-compose.yml (complet)
services:
wp:
build:
context: .
dockerfile: docker/Dockerfile
container_name: wp_app
env_file: .env
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
mailpit:
condition: service_started
ports:
- "${HTTP_PORT:-8080}:80"
volumes:
- ./wp:/var/www/html
- ./wp-content:/var/www/html/wp-content
- ./logs:/var/log/wp
extra_hosts:
- "host.docker.internal:host-gateway"
db:
image: mariadb:10.11
container_name: wp_db
environment:
MYSQL_DATABASE: ${DB_NAME:-wordpress}
MYSQL_USER: ${DB_USER:-wordpress}
MYSQL_PASSWORD: ${DB_PASSWORD:-wordpress}
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root}
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "127.0.0.1", "-uroot", "-p${DB_ROOT_PASSWORD}"]
interval: 5s
timeout: 3s
retries: 30
redis:
image: redis:7-alpine
container_name: wp_redis
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
mailpit:
image: axllent/mailpit:latest
container_name: wp_mailpit
ports:
- "${MAILPIT_PORT:-8025}:8025"
cron:
image: curlimages/curl:8.7.1
container_name: wp_cron
depends_on:
- wp
command: >
sh -lc "while true; do
curl -sS http://wp/wp-cron.php?doing_wp_cron=1 > /dev/null || true;
sleep 60;
done"
volumes:
db_data:
redis_data:
Personnalisations utiles
- Changer
HTTP_PORTsi vous avez déjà un serveur local sur 8080. - Remplacer MariaDB par MySQL 8 si votre prod est en MySQL (attention aux collations/SQL modes).
- Activer Redis côté WP en installant/configurant correctement le plugin (voir section maintenance).
Adapter pour Divi 5 / Elementor / Avada
Les builders ne changent pas Docker, mais ils changent vos contraintes : mémoire, temps d’exécution, taille d’upload, et parfois des dépendances PHP (zip, intl, gd/imagemagick).
Divi 5
- Augmentez
memory_limit(déjà à 512M) si vous importez des layouts lourds. - Gardez
wp-contentversionné partiellement : thème enfant + mu-plugins, mais pas les caches.
Elementor
- Elementor déclenche souvent des requêtes AJAX longues :
max_execution_timeà 120s évite des faux négatifs. - Si des CSS/JS ne se chargent pas : vérifiez les permaliens et les headers cache (souvent un mauvais
AllowOverrideou un cache navigateur).
Avada (Fusion Builder)
- Imports de démos : assurez-vous d’avoir
zipetintl(installés dans le Dockerfile), sinon vous aurez des erreurs silencieuses. - Avada peut générer beaucoup de fichiers : gardez
wp-content/uploadsen volume (c’est déjà le cas via./wp-content).
Vérification finale
- Accès front : http://localhost:8080 affiche le site.
- Accès admin :
/wp-adminfonctionne, login OK. - Permaliens : un article en
/%postname%/ne renvoie pas 404. - Logs : une erreur PHP volontaire apparaît dans
wp-content/debug.log(testez avec unerror_log('test')dans un mu-plugin). - Mail : un
wp_mail()apparaît dans Mailpit. - Xdebug : en ajoutant
?XDEBUG_TRIGGER=1, votre IDE accroche une requête (si configuré).
Si le résultat n’est pas celui attendu
Voici un tableau de diagnostic que j’utilise en premier passage. Il couvre les symptômes les plus fréquents sur des stacks Docker WordPress.
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
| Page blanche / 500 | Erreur PHP, plugin incompatible, mémoire | Lire logs/apache-error.log et wp-content/debug.log |
Désactiver plugin via WP-CLI, augmenter memory_limit, corriger l’erreur |
| Erreur “Error establishing a database connection” | DB pas prête, mauvais identifiants, volumes corrompus | docker compose logs db + test mariadb-admin ping |
Vérifier .env, supprimer volume DB et relancer install |
| Permaliens 404 | mod_rewrite off / AllowOverride bloqué | Vérifier Apache conf et a2enmod rewrite |
Rebuild image, relancer wp rewrite structure ... --hard |
| Xdebug ne se connecte pas | Mauvais host/port, trigger absent | IDE écoute 9003 ? trigger envoyé ? | Configurer IDE, utiliser host.docker.internal, vérifier firewall |
| Les e-mails n’apparaissent pas | Plugin SMTP override, PHPMailer non configuré | Tester wp_mail() + logs |
Activer le mu-plugin Mailpit, vérifier host mailpit port 1025 |
Commandes de dépannage (réflexes)
# Voir l'état
docker compose ps
# Logs d'un service
docker compose logs -f wp
docker compose logs -f db
# Entrer dans le conteneur WP
docker compose exec wp bash
# Désactiver tous les plugins (si wp-admin inaccessible)
docker compose exec wp wp plugin deactivate --all --allow-root
# Reconstruire proprement
docker compose down
docker compose up -d --build
Pièges et erreurs courantes
| Erreur | Cause | Solution |
|---|---|---|
Copier un snippet dans functions.php du mauvais thème |
Vous éditez le thème parent au lieu du thème enfant | Versionnez un thème enfant, ou utilisez un mu-plugin pour le code dev |
| Oublier un point-virgule dans un mu-plugin | Parse error immédiate, parfois page blanche | Regarder debug.log et apache-error.log, corriger, recharger |
| Confusion action/filtre (hook inadapté) | Le code ne s’exécute pas, ou trop tôt | Utiliser init/wp_loaded selon le besoin, vérifier priorité |
| Tester un ancien tutoriel (WP 5.x) avec du code obsolète | Fonctions/constantes changées, comportement différent | Vérifier la doc officielle WP 6.9+ sur developer.wordpress.org |
| CSS/JS non chargés | Mauvais wp_enqueue_script, mauvais hook, cache navigateur |
Enqueue sur wp_enqueue_scripts/admin_enqueue_scripts, vider cache |
| WP-CLI échoue “Could not connect to database” | DB pas prête (race condition) | Healthcheck + attente dans entrypoint (déjà inclus) |
| Erreur liée à PHP trop ancien | Vous avez changé l’image PHP sans rebuild | docker compose build --no-cache, vérifier php -v dans le conteneur |
Variante / alternative
Alternative 1 : LocalWP / DevKinsta (sans Docker “à la main”)
Si votre objectif est surtout de développer un thème ou un site builder sans gérer l’infra, des outils comme LocalWP peuvent être plus rapides à prendre en main. Le revers : la reproductibilité en équipe est moins “déclarative” qu’un repo Docker, et l’intégration CI est souvent plus compliquée.
Alternative 2 (avancée) : Traefik + HTTPS + domaines multiples
Quand vous gérez 10 projets en parallèle, je passe souvent sur Traefik pour éviter la guerre des ports et avoir du HTTPS local. C’est plus long à configurer, mais ensuite vous avez :
https://projet-a.test,https://projet-b.test…- certificats auto (mkcert ou Traefik + CA locale)
- une stack multi-projets propre
Je ne l’active pas par défaut ici pour rester focalisé sur WordPress, mais c’est une extension naturelle.
Conseils sécurité, performance et maintenance
Sécurité
- Ne publiez pas votre port 3306 (DB) vers l’hôte si ce n’est pas nécessaire.
- Gardez
WP_DEBUG_DISPLAYàfalsemême en dev : vous évitez de “debugger” via HTML cassé. - Si vous exposez le site sur un réseau (démo client), changez les mots de passe et limitez l’accès.
Performance
- Xdebug en trigger : c’est déjà un gros gain.
- Évitez d’activer un cache agressif en dev (minify, concat) tant que vous débuggez.
- Opcache : en dev pur, je le laisse souvent désactivé. Si vous voulez simuler la prod, activez-le et invalidez proprement.
Maintenance
- Figez les versions d’images (MariaDB, Redis). Évitez
latestsauf pour un service non critique (Mailpit peut rester en latest). - Mettez à jour WordPress via WP-CLI, pas en “cliquant” : vous gardez un historique et vous pouvez automatiser.
Commandes utiles :
# Mettre à jour WP (exemple)
docker compose exec wp wp core update --allow-root
# Vérifier version WP
docker compose exec wp wp core version --allow-root
# Vérifier PHP
docker compose exec wp php -v
# Nettoyer volumes (ATTENTION: supprime la DB)
docker compose down -v
Pour aller plus loin
- Ajouter un service phpMyAdmin (ou Adminer) uniquement si votre équipe en a besoin. Je préfère WP-CLI + dumps SQL, mais certains workflows exigent une UI.
- Ajouter un Makefile (ou
justfile) pour standardiser les commandes (make up,make reset,make test). - Mettre en place un import DB “safe” : dump compressé + remplacement d’URLs via
wp search-replaceavec--skip-columns=guid. - Créer une image “prod-like” (sans Xdebug, avec opcache) et une image “dev”. Même Compose, deux profils.
Ressources
- WordPress Developer Resources — wp-config.php
- WordPress Developer Resources — WP-CLI Commands
- WordPress.org — Debugging in WordPress
- GitHub — WP-CLI
- GitHub — WordPress (mirror)
- WordPress Core Trac
- PHP.net — PHP 8.3 releases
- Docker Docs — Compose
FAQ
Pourquoi monter ./wp en volume au lieu de builder le core dans l’image ?
Parce que vous voulez pouvoir changer rapidement de version WordPress (6.9.4 → 6.9.5) via WP-CLI et versionner vos scripts, pas reconstruire une image à chaque patch. Pour des projets très contrôlés, vous pouvez aussi “vendoriser” le core, mais c’est une autre philosophie.
Est-ce que ce setup marche avec PHP 8.1 exactement ?
Oui si vous changez l’image de base (wordpress:php8.1-apache). Je vise PHP 8.3 ici parce que ça reflète mieux les environnements actuels. Si votre prod est en 8.1, alignez-vous dessus pour éviter les surprises.
Comment importer une base client ?
Le plus fiable : dump SQL + wp db import puis wp search-replace. Exemple :
docker compose exec -T wp wp db import /var/www/html/wp-content/uploads/dump.sql --allow-root
docker compose exec wp wp search-replace 'https://www.site-client.tld' 'http://localhost:8080' --skip-columns=guid --allow-root
Pourquoi WP_CONTENT_URL est fixé à http://localhost/wp-content dans l’entrypoint ?
C’est un raccourci qui peut être faux si vous changez le port. Si vous utilisez un port différent, ajustez-le (ou supprimez ces deux constantes et laissez WordPress gérer). Je l’ai vu résoudre des cas tordus quand des reverse proxies locaux réécrivent les headers, mais ce n’est pas obligatoire.
Redis est lancé, mais WordPress ne l’utilise pas. Normal ?
Oui. Redis côté serveur ne suffit pas : il faut un plugin de cache objet (ex. redis-cache) et, selon le plugin, activer le cache via WP-CLI ou l’admin. En dev, je l’installe mais je ne l’active pas toujours.
Pourquoi Query Monitor en dev ?
Parce que c’est le moyen le plus rapide d’identifier une requête lente, un hook appelé trop souvent, ou un appel HTTP bloquant. Sur des sites builder, c’est souvent la différence entre “je suppose” et “je sais”.
Je vois “Permission denied” sur des fichiers dans wp-content. Que faire ?
Ça arrive quand l’UID/GID de l’hôte et du conteneur ne sont pas alignés (surtout Linux). La solution propre est d’exécuter Apache/PHP avec un user correspondant, ou de corriger les permissions sur votre dossier projet. Évitez le réflexe chmod -R 777.
Est-ce que je peux utiliser Nginx au lieu d’Apache ?
Oui, mais vous devrez gérer PHP-FPM, la config Nginx et les règles de permaliens. Apache est plus direct pour un tutoriel reproductible, surtout si vous voulez minimiser les différences entre environnements.
Comment vérifier la version exacte de WordPress dans le conteneur ?
docker compose exec wp wp core version --allow-root
Où suivre les changements core si un comportement change entre 6.9.x et 6.10 ?
Regardez les tickets et commits sur Trac et GitHub :