Wordpress & docker swarm
Posted on sam. 31 mai 2025 in devops
Dans un effort de limiter les coûts de mes trop nombreux serveurs, je me suis mis en tête de dépeupler un de mes serveurs OVH en vue de la décommissionner. Entre autre chose, ce serveur héberge un Wordpress que je vais rapatrier sur un autre serveur.
Petit problème, je ne veux pas installer toute la stack PHP/FPM sur une belle debian toute propre. Heureusement pour moi, c’est exactement (pas du tout) pour ça que docker a été créé. Et encore heureusement pour moi il se trouve que wordpress ship sa propre image docker et je n’aurai même pas à mettre les mains dans le cambouis Que je suis heureux (et naïf).
Après avoir longtemps lutté pour faire fonctionner proprement tout ça, j’ai décidé de documenter toute la stack ici. Des fois que ça aiderait une autre âme perdue.
Première approche
Ok donc il suffit que je passe d’une architecture où un nginx fait appel à un démon php fpm, à une architecture où un nginx fait appel à un démon PHP-FPM dans un docker. Simple.
Ça serait aussi le moment de tout dockeriser, à commencer par les fichiers wordpress à mettre dans un docker volumes
et des certificats ssl à mettre dans un docker configs
.
(On va considérer que le montage d’un docker avec MySQL est assez simple pour être passé en ellipse)
Je vous passe la rétro ingéniérie nécessaire à ma compréhension du fonctionnement de Wordpress. Que ce soit le démon avec PHP-FPM ou dockerisé, il a besoin d’un nginx ou d’un apache pour servir les fichiers statics. Un process php seul ne fera jamais le taff.
Mais même ainsi, on reste face à plusieurs problèmes :
- docker “supporte l’IPv6”. Mais il faut vraiment insister sur les guillemets (dans le sens où, non il ne le supporte pas), si je veux garder la connectivité il va me falloir un proxy http hors de docker.
- wordpress fait des requêtes de “loopback”. Ce sont des requêtes où il tente de se crawl lui même pour déclencher des cron (très élégants)
Complexification
Après beaucoup de prise de tête à comprendre pourquoi ça ne marche pas simplement, j’en arrive à la conclusion que ça ne pourra pas être simple et qu’il va falloir complexifier la stack.
Aparté, c’est un de ces moments en informatique où les choses qui se conçoivent simplement et qui devaient s’emboîter sans effort coincent. Alors on rajoute des couches de complexité jusqu’à ce que ça fonctionne. C’est alors qu’on éprouve un sentiment doux-amer de satisfaction d’avoir réussi, mêlée à la déception face à la laideur de la solution…
Donc pour rentrer un peu dans le détails de cette nouvelle implémentation on a :
- un proxy http qui fait la validation SSL avec les certificats disponibles sur le disque de la machine
- un nginx dockerisé capable de répondre à une requête de loopback. Pour cela il lui faut :
- être résolvable avec l’adresse publique du wordpress (voir le
hostname
dans la configuration docker ci-dessous) - avoir accès aux certificats SSL aussi utilisés par le proxy (ici fait via
docker configs
mais ça peut être via un volume comme le reste)
- être résolvable avec l’adresse publique du wordpress (voir le
Ce qui nous donne la configuration suivante :
services:
my-wp-nginx:
hostname: www.my-wp.com # l'adresse publique du wordpress
image: nginx
networks:
- ntw_nginx
ports: ["0.0.0.0:8101:80"] # le port à référencer dans la config du proxy
deploy:
mode: replicated
replicas: 4
configs:
- source: nginx_config
target: /etc/nginx/nginx.conf # la config nginx référencée après, qui redirigera vers fpm
- source: wp_ssl_fullchain
target: /etc/fullchain.pem
- source: wp_ssl_privkey
target: /etc/privkey.pem
volumes:
- mywpfiles:/var/www/html
my-wp-fpm:
image: wordpress:6.7-php8.2-fpm
hostname: my-wp.fpm # le nom résolvable par nginx pour cibler le conteneur php
networks:
- ntw_nginx
- ntw_mysql
- ntw_redis
deploy:
mode: replicated
replicas: 2
environment:
WORDPRESS_DB_HOST: mysql.db
WORDPRESS_DB_USER: my-wp
WORDPRESS_DB_NAME: my-wp
WORDPRESS_DB_PASSWORD: my-wp-pwd
volumes:
- mywpfiles:/var/www/html
volumes:
mywpfiles:
name: mywpfiles
Nginx config
server {
listen 443 ssl;
ssl_certificate /etc/fullchain.pem;
ssl_certificate_key /etc/privkey.pem;
server_name www.my-wp.com;
root /var/www/html/;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~* \.(?:ico|css|js|gif|jpe?g|png|woff2?|eot|ttf|svg|mp4)$ {
expires max;
log_not_found off;
access_log off;
try_files $uri =404;
}
# Send PHP requests to PHP-FPM
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass my-wp.fpm:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; # Use the path INSIDE the FPM container
}
}
Conclusion
Il est regrettable que le wordpress (ou bien le docker officiel) soit incapable d’être utilisé seul. Le fait qu’il faille deux logiciels pour réussir à faire tourner Wordpress n’est pas vraiment explicite dans leur documentation ou dans leurs exemples.
La gestion des configs avec docker swarm est mal-aisée pour être poli. On ne peut pas mettre à jour une config docker, donc il faut en déployer une nouvelle avec un nom qui ne collisionne pas, mettre à jour le container puis ensuite nettoyer les configs orphelines. J’ai violement scripté ça, parce que ça devient pénible à la deuxième typo dans une configuration nginx.
Mais après en avoir sué, cette configuration me convient et a le bon goût de pouvoir monter en charge assez facilement.
Annexe
Code for chart 1
flowchart TD
subgraph WebServer["WebServer"]
disk["Disk"]
nginx["nginx"]
configs["SSL Certs"]
FPM["wordpress dockerized"]
SQL["MySQL"]
end
I["Internet"] <-- ipv4 & ipv6 --> nginx
nginx -- php exec --> FPM
nginx --> configs
FPM -- db conn --> SQL
FPM -- read code --> disk
nginx -- read static files --> disk
SQL --> disk
nginx@{ shape: procs}
FPM@{ shape: procs}
SQL@{ shape: db}
disk@{ shape: disk}
I@{ shape: rounded}
I:::Sky
classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C
Code for chart 2
flowchart TD
subgraph Dockers["Dockers"]
nginx["nginx"]
configs["configs"]
FPM["wordpress dockerized"]
SQL["MySQL"]
volumes["volumes"]
end
subgraph WebServer["WebServer"]
Dockers
disk["Disk"]
end
I["Internet"] <-- ipv4 & ipv6 --> nginx
nginx -- php exec --> FPM
nginx -- read SSL certs --> configs
FPM -- db conn --> SQL
FPM -- read code --> volumes
nginx -- read static files --> volumes
volumes --> disk
SQL --> volumes
nginx@{ shape: procs}
FPM@{ shape: procs}
SQL@{ shape: db}
disk@{ shape: disk}
I@{ shape: rounded}
I:::Sky
classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C
Code for chart 3
flowchart TD
subgraph Dockers["Dockers"]
nginx["nginx"]
configs["configs"]
FPM["wordpress dockerized"]
SQL["MySQL"]
volumes["volumes"]
end
subgraph WebServer["WebServer"]
proxy["http proxy"]
Dockers
disk["Disk"]
SSL["SSLCerts"]
end
I["Internet"] <-- ipv4 & ipv6 --> proxy
proxy --> nginx
proxy -- SSL Certs --> SSL
SSL -- stored --> disk
SSL -- loaded --> configs
nginx -- php exec --> FPM
FPM -- loopback request --> nginx
nginx -- access for loopback --> configs
FPM -- db conn --> SQL
FPM -- read code --> volumes
nginx -- read static files --> volumes
volumes --> disk
SQL --> volumes
nginx@{ shape: procs}
FPM@{ shape: procs}
SQL@{ shape: db}
disk@{ shape: disk}
I@{ shape: rounded}
I:::Sky
classDef Sky stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#374D7C