Pourquoi cette stack ?

Héberger WordPress sur un serveur Linux avec Docker, c’est choisir la liberté, la performance et l’évolutivité. Fini les dépendances système qui se marchent dessus, fini les mises à jour PHP qui cassent votre site, fini les hébergeurs mutualisés qui surchargent vos ressources. Avec Docker, chaque composant de votre infrastructure vit dans son propre conteneur, isolé et configurable à l’infini.

Dans ce guide, nous allons monter une stack complète et production-ready, composée de six conteneur complémentaires :

  • WordPress : Le CMS le plus utilisé au monde (43 % du web)
  • MariaDB : La base de données, fork performant de MySQL
  • Redis : Le cache objet en mémoire pour accélérer WordPress
  • Cloudflare Tunnel : L’exposition sécurisée sur internet sans ouvrir de port
  • CrowdSec : La protection collaborative contre les attaques
  • Duplicati : Les sauvegardes automatiques chiffrées et externalisées

Cette architecture fonctionne sur n’importe quel serveur Linux un VPS à 3000 XOF/mois, un vieux PC recyclé en serveur, ou un NAS Synology. Et dans tous les cas, votre site sera accessible via HTTPS avec un certificat SSL valide, sans avoir besoin d’une IP publique fixe ni d’ouvrir le moindre port dans votre box ou votre pare-feu.

LIRE AUSSI : Mail-Archiver : Archivez Automatiquement Tous Vos Emails en 5 étapes

2. Architecture de la solution

Avant de taper la première commande, prenons un moment pour comprendre comment s’articulent les différents composants. Une bonne architecture se comprend, se maintient et s’améliore. Voici le flux d’une requête HTTP vers votre WordPress :

Cloudflare : joue le rôle de proxy inverse. Il absorbe les pics de trafic, protège contre les attaques DDoS et génère le certificat SSL automatiquement. Votre serveur n’est jamais directement exposé sur internet.

Cloudflare Tunnel : crée un tunnel chiffré sortant (outbound) depuis votre serveur vers les datacenters Cloudflare. Pas besoin d’IP fixe, pas de port ouvert, l’équivalent d’un VPN à sens unique géré par Cloudflare.

CrowdSec : analyse les logs en temps réel et bloque automatiquement les IPs qui adoptent un comportement suspect (brute-force, scan de ports, requêtes WordPress malveillantes). Sa base de données de menaces est partagée entre des millions d’instances dans le monde.

Redis : stocke en mémoire les résultats des requêtes PHP les plus fréquentes. Au lieu d’interroger MariaDB à chaque visite, WordPress récupère la réponse depuis Redis en quelques microsecondes.

3. Prérequis

Avant de commencer, vérifiez que vous disposez des éléments suivants :

  • ✅ Un serveur sous Ubuntu 22.04 LTS ou Debian 12 (avec accès root ou sudo)
  • ✅ Un nom de domaine actif (ex : monsite.com), connecté à Cloudflare
  • ✅ Un compte Cloudflare gratuit avec votre domaine ajouté
  • ✅ Accès SSH au serveur
  • ✅ Au minimum 1 Go de RAM et 10 Go d’espace disque SSD

Commencez par mettre à jour le système :

sudo apt update && sudo apt upgrade -y
sudo apt install curl wget git nano -y
Bash

Étape 1: Installer Docker et Docker Compose

Nous allons installer Docker depuis les dépôts officiels de Docker, et non depuis les dépôts Ubuntu qui contiennent souvent une version plus ancienne.

1.1 : Ajouter le dépôt officiel Docker

# Ajouter la clé GPG officielle Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Ajouter le dépôt Docker
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Bash

1.2 : Installer Docker Engine et le plugin Compose

sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
Bash

1.3 : Ajouter votre utilisateur au groupe Docker

Cette étape vous permet d’utiliser Docker sans taper sudo à chaque commande. Reconnectez-vous à votre session SSH après cette commande pour que le changement soit pris en compte.

sudo usermod -aG docker $USER
# Puis déconnectez-vous et reconnectez-vous en SSH
Bash

1.4 : Vérifier l’installation

docker --version
# Docker version 26.x.x, build ...

docker compose version
# Docker Compose version v2.x.x
Bash

Étape 2 : Préparer la structure du projet

Une bonne organisation des fichiers est fondamentale. Créez l’arborescence du projet avec ces commandes :

mkdir -p ~/wordpress-stack/{nginx,backups,duplicati_config,crowdsec}
cd ~/wordpress-stack
Bash

Votre arborescence ressemblera à ceci :

~/wordpress-stack/
├── docker-compose.yml      # Orchestrateur principal
├── .env                    # Variables d'environnement (secrets)
├── nginx/
   └── wordpress.conf      # Configuration Nginx
├── backups/                # Sauvegardes locales (Duplicati)
└── duplicati_config/       # Configuration Duplicati
Bash

2.1 : Créer le fichier des variables d’environnement

Le fichier .env regroupe tous vos secrets. Il ne doit jamais être commité dans Git. Générez des mots de passe robustes avec openssl rand -base64 32.

nano ~/wordpress-stack/.env
Bash
# ── BASE DE DONNÉES ──────────────────────────────────────────
MYSQL_ROOT_PASSWORD=MotDePasseRootSuperSecret2025!
MYSQL_DATABASE=wordpress_db
MYSQL_USER=wp_user
MYSQL_PASSWORD=MotDePasseWPSuperSecret2025!

# ── WORDPRESS ─────────────────────────────────────────────────
WORDPRESS_DB_HOST=mariadb:3306
WORDPRESS_DB_NAME=wordpress_db
WORDPRESS_DB_USER=wp_user
WORDPRESS_DB_PASSWORD=MotDePasseWPSuperSecret2025!
WORDPRESS_TABLE_PREFIX=k9x_

# ── DOMAINE ───────────────────────────────────────────────────
DOMAIN=monsite.com
Bash

Étape 3 : Le fichier docker-compose.yml

C’est le cœur de votre infrastructure. Ce fichier unique décrit tous les conteneurs, leurs paramètres, leurs dépendances et leurs volumes persistants.

nano ~/wordpress-stack/docker-compose.yml
Bash
version: "3.9"

networks:
  wp_net:
    driver: bridge

volumes:
  wordpress_data:
  db_data:
  redis_data:
  crowdsec_db:
  crowdsec_config:

services:

  # ── MARIADB ──────────────────────────────────────────────────
  mariadb:
    image: mariadb:11
    container_name: wp_mariadb
    restart: unless-stopped
    env_file: .env
    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
    networks:
      - wp_net
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 20s
      timeout: 5s
      retries: 5
    command: >
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --innodb-buffer-pool-size=256M
      --max-connections=100

  # ── REDIS ─────────────────────────────────────────────────────
  redis:
    image: redis:7-alpine
    container_name: wp_redis
    restart: unless-stopped
    command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru --save ""
    volumes:
      - redis_data:/data
    networks:
      - wp_net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  # ── WORDPRESS (PHP-FPM) ────────────────────────────────────────
  wordpress:
    image: wordpress:6-php8.2-fpm-alpine
    container_name: wp_app
    restart: unless-stopped
    env_file: .env
    depends_on:
      mariadb:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: ${WORDPRESS_DB_HOST}
      WORDPRESS_DB_NAME: ${WORDPRESS_DB_NAME}
      WORDPRESS_DB_USER: ${WORDPRESS_DB_USER}
      WORDPRESS_DB_PASSWORD: ${WORDPRESS_DB_PASSWORD}
      WORDPRESS_TABLE_PREFIX: ${WORDPRESS_TABLE_PREFIX}
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_REDIS_HOST', 'redis');
        define('WP_REDIS_PORT', 6379);
        define('WP_CACHE', true);
        define('DISALLOW_FILE_EDIT', true);
        define('WP_DEBUG', false);
        define('FORCE_SSL_ADMIN', true);
    volumes:
      - wordpress_data:/var/www/html
    networks:
      - wp_net

  # ── NGINX (reverse proxy) ─────────────────────────────────────
  nginx:
    image: nginx:alpine
    container_name: wp_nginx
    restart: unless-stopped
    depends_on:
      - wordpress
    volumes:
      - wordpress_data:/var/www/html:ro
      - ./nginx/wordpress.conf:/etc/nginx/conf.d/default.conf:ro
    ports:
      - "8080:80"
    networks:
      - wp_net

  # ── CROWDSEC ──────────────────────────────────────────────────
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: wp_crowdsec
    restart: unless-stopped
    environment:
      GID: "1000"
      COLLECTIONS: "crowdsecurity/nginx crowdsecurity/wordpress"
    volumes:
      - /var/log/nginx:/var/log/nginx:ro
      - crowdsec_db:/var/lib/crowdsec/data
      - crowdsec_config:/etc/crowdsec
    networks:
      - wp_net

  # ── CROWDSEC BOUNCER ──────────────────────────────────────────
  cs-bouncer:
    image: fbonalair/traefik-crowdsec-bouncer:latest
    container_name: wp_cs_bouncer
    restart: unless-stopped
    environment:
      CROWDSEC_BOUNCER_API_KEY: ${CROWDSEC_BOUNCER_API_KEY:-}
      CROWDSEC_AGENT_HOST: crowdsec:8080
      GIN_MODE: release
    networks:
      - wp_net
    depends_on:
      - crowdsec

  # ── DUPLICATI (sauvegardes) ───────────────────────────────────
  duplicati:
    image: lscr.io/linuxserver/duplicati:latest
    container_name: wp_duplicati
    restart: unless-stopped
    environment:
      PUID: 1000
      PGID: 1000
      TZ: Africa/Lome
    volumes:
      - ./duplicati_config:/config
      - ./backups:/backups
      - wordpress_data:/source/wordpress:ro
      - db_data:/source/db:ro
    ports:
      - "127.0.0.1:8200:8200"
    networks:
      - wp_net
YAML

3.1 : Configuration Nginx

nano ~/wordpress-stack/nginx/wordpress.conf
Bash
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=1r/s;

server {
    listen 80;
    server_name _;
    root /var/www/html;
    index index.php index.html;
    client_max_body_size 64M;

    # Gestion des permaliens WordPress
    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # PHP-FPM via le conteneur WordPress
    location ~ \.php$ {
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 300;
    }

    # Protection de wp-login.php (rate limiting)
    location = /wp-login.php {
        limit_req zone=wp_login burst=3 nodelay;
        fastcgi_pass wordpress:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    # Bloquer xmlrpc.php (vecteur d'attaque courant)
    location = /xmlrpc.php {
        return 403;
    }

    # Bloquer l'accès aux fichiers sensibles
    location ~ /\.(ht|git|env) {
        deny all;
    }

    # Cache navigateur longue durée pour les assets statiques
    location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|webp|woff2|ttf)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;
}
Nginx

Étape 4 : Lancer la stack

Tout est en place. Démarrez l’ensemble des conteneurs :

cd ~/wordpress-stack

# Démarrer tous les services en arrière-plan
docker compose up -d

# Suivre les logs en temps réel (Ctrl+C pour quitter)
docker compose logs -f
Bash

Après quelques secondes, vérifiez que tous les conteneurs sont démarrés :

docker compose ps
Bash

Tous les services doivent afficher le statut running ou healthy. Testez l’accès local à WordPress :

curl -I http://localhost:8080
# HTTP/1.1 200 OK ou 302 Found → WordPress est fonctionnel ✅
Bash

Étape 5 : Cloudflare Tunnel : exposition sécurisée

Cloudflare Tunnel permet d’exposer votre serveur local sur internet sans ouvrir de port dans votre pare-feu et sans IP publique fixe. Le trafic transite via les serveurs Cloudflare, qui gèrent le SSL, la protection DDoS et le CDN de manière entièrement transparente.

5.1 : Installer cloudflared

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb \
  -o cloudflared.deb
sudo dpkg -i cloudflared.deb
cloudflared --version
Bash

5.2 : S’authentifier et créer le tunnel

# Authentification (ouvrez le lien dans votre navigateur)
cloudflared tunnel login

# Créer le tunnel (remplacez "wp-monsite" par le nom de votre choix)
cloudflared tunnel create wp-monsite
Bash

La commande create génère un UUID et un fichier de credentials JSON dans ~/.cloudflared/. Notez l’UUID, il est nécessaire à l’étape suivante.

5.3 : Configurer le tunnel

nano ~/.cloudflared/config.yml
Bash
tunnel: <UUID-DU-TUNNEL>
credentials-file: /root/.cloudflared/<UUID-DU-TUNNEL>.json

ingress:
  - hostname: monsite.com
    service: http://localhost:8080
  - hostname: www.monsite.com
    service: http://localhost:8080
  - service: http_status:404
Bash

5.4 : Créer les entrées DNS et activer le service

# Créer les enregistrements CNAME automatiquement dans Cloudflare
cloudflared tunnel route dns wp-monsite monsite.com
cloudflared tunnel route dns wp-monsite www.monsite.com

# Installer le tunnel comme service système (démarrage automatique)
sudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflared
sudo systemctl status cloudflared
Bash

Votre WordPress est maintenant accessible sur https://monsite.com avec SSL valide — entièrement géré par Cloudflare, sans Certbot ni renouvellement à planifier.

Étape 6 : CrowdSec : protection collaborative

CrowdSec analyse vos logs en temps réel et bloque automatiquement les IPs malveillantes. Sa force : l’intelligence collective. Lorsqu’une IP attaque une instance CrowdSec quelque part dans le monde, l’information est partagée avec toutes les instances y compris la vôtre.

6.1 : Vérifier et installer les collections

# Entrer dans le conteneur CrowdSec
docker exec -it wp_crowdsec /bin/sh

# Vérifier les collections installées
cscli collections list

# Si absent, les installer manuellement
cscli collections install crowdsecurity/nginx
cscli collections install crowdsecurity/wordpress
cscli hub update

exit
Bash

6.2 : Générer la clé API pour le bouncer

docker exec wp_crowdsec cscli bouncers add wp-nginx-bouncer
# → Copiez la clé API affichée, vous en aurez besoin à l'étape suivante
Bash

Ajoutez la clé dans votre fichier .env :

echo "CROWDSEC_BOUNCER_API_KEY=votre_cle_api" >> ~/wordpress-stack/.env

# Relancer le bouncer avec la nouvelle clé
docker compose up -d --force-recreate cs-bouncer
Bash

6.3 : Surveiller les attaques en temps réel

# Lister les IPs actuellement bannies
docker exec wp_crowdsec cscli decisions list

# Voir les alertes déclenchées
docker exec wp_crowdsec cscli alerts list

# Bannir manuellement une IP suspecte pour 24h
docker exec wp_crowdsec cscli decisions add --ip 1.2.3.4 --reason "Suspect" --duration 24h

# Rejoindre la communauté CrowdSec (intelligence partagée)
docker exec wp_crowdsec cscli console enroll <votre-enrollment-key>
# Obtenez votre clé sur https://app.crowdsec.net (inscription gratuite)
Bash

Étape 7 : Configurer WordPress

7.1 : Premier accès et installation

Accédez à https://monsite.com dans votre navigateur. L’assistant d’installation WordPress s’affiche. Renseignez le titre du site, le nom d’utilisateur administrateur, un mot de passe fort et une adresse email. Cliquez sur Installer WordPress.

7.2 : Activer le cache Redis

Installez le plugin Redis Object Cache depuis Extensions → Ajouter → rechercher « Redis Object Cache ». Activez-le, puis allez dans Réglages → Redis et cliquez sur Activer le cache objet. Vérifiez la connexion :

docker exec wp_app wp redis status --allow-root
# → Status: Connected ✅
Bash

7.3 : Durcir wp-config.php

La variable WORDPRESS_CONFIG_EXTRA du docker-compose.yml injecte automatique ou manuellement ces lignes dans wp-config.php. Vous pouvez en ajouter d’autres en modifiant cette variable, sans entrer dans le conteneur :

WORDPRESS_CONFIG_EXTRA: |
  define('WP_REDIS_HOST', 'redis');
  define('WP_REDIS_PORT', 6379);
  define('WP_CACHE', true);
  define('DISALLOW_FILE_EDIT', true);
  define('FORCE_SSL_ADMIN', true);
  define('WP_POST_REVISIONS', 5);
  define('EMPTY_TRASH_DAYS', 30);
  define('WP_DEBUG', false);
PHP

Après modification du docker-compose.yml, appliquez les changements :

docker compose up -d --force-recreate wordpress
Bash

Étape 8 : Sauvegardes avec Duplicati

Même la meilleure infrastructure peut subir une défaillance. Duplicati est une solution open source qui effectue des sauvegardes chiffrées AES-256, compressées et externalisées vers de nombreuses destinations : Google Drive, Backblaze B2, Amazon S3, Synology NAS, SFTP, OneDrive…

8.1 : Accéder à l’interface Duplicati

Le conteneur Duplicati expose son interface sur le port 8200, uniquement accessible en local (lié à 127.0.0.1). Créez un tunnel SSH depuis votre machine pour y accéder :

# Sur votre machine locale (pas le serveur)
ssh -L 8200:localhost:8200 user@IP-DU-SERVEUR
Bash

Ouvrez ensuite http://localhost:8200 dans votre navigateur.

8.2 : Configurer une tâche de sauvegarde

Cliquez sur Ajouter une sauvegarde et configurez les paramètres suivants :

  • Nom : Sauvegarde WordPress Production
  • Chiffrement : AES-256 — définissez une phrase secrète et conservez-la précieusement
  • Destination : Backblaze B2 (10 Go gratuits), Google Drive, SFTP ou votre NAS Synology
  • Sources : /source/wordpress (fichiers WP) et /source/db (données
    MariaDB)
  • Planification : Quotidien à 2h00 du matin
  • Rétention : 30 sauvegardes (environ 1 mois d’historique)

8.3 : Sauvegarde manuelle de la base de données

Pour une sauvegarde ponctuelle avant une mise à jour majeure :

docker exec wp_mariadb mysqldump \
  -u wp_user -p'MotDePasseWPSuperSecret2025!' wordpress_db \
  | gzip > ~/wordpress-stack/backups/db-$(date +%Y%m%d_%H%M).sql.gz

# Vérifier le fichier créé
ls -lh ~/wordpress-stack/backups/
Bash

Commandes utiles au quotidien

Gestion des conteneurs

# Démarrer / arrêter tous les conteneurs
docker compose up -d
docker compose down

# Redémarrer un service spécifique
docker compose restart wordpress

# Voir l'état de tous les conteneurs
docker compose ps

# Logs en temps réel d'un service
docker compose logs -f wordpress
Bash

Mises à jour

# Récupérer les nouvelles versions des images
docker compose pull

# Relancer avec les nouvelles images (sans interruption prolongée)
docker compose up -d --remove-orphans

# Nettoyer les anciennes images
docker image prune -f
Bash

Surveillance des ressources

# Consommation CPU et RAM en temps réel
docker stats

# Espace disque utilisé par les volumes
docker system df -v

# Test Redis
docker exec wp_redis redis-cli ping
# → PONG ✅
Bash

Peut-on héberger plusieurs sites WordPress avec cette stack ?

Oui. Ajoutez simplement des règles supplémentaires dans la configuration Cloudflare Tunnel, et créez une base de données supplémentaire dans MariaDB pour chaque site. Pour une gestion multi-sites avancée, considérez Nginx Proxy Manager comme reverse proxy central à la place du conteneur Nginx seul.

Cloudflare Tunnel est-il gratuit ?

Oui, la fonctionnalité Tunnel est disponible dans le plan Free de Cloudflare. Ce plan inclut la protection DDoS, le CDN mondial et le SSL automatique tout ce dont vous avez besoin pour un usage personnel ou professionnel standard.

CrowdSec vs Fail2Ban : quelle différence ?

Fail2Ban analyse uniquement vos logs locaux et ne partage aucune information. CrowdSec fait la même analyse, mais partage et reçoit des informations de menaces avec une communauté mondiale. Résultat : votre serveur bénéficie d’une liste de blocage enrichie en permanence, sans effort supplémentaire de votre part.

Vous venez de mettre en place une infrastructure WordPress professionnelle sur Linux, entièrement conteneurisée avec Docker. Cette stack vous offre ce qu’aucun hébergeur mutualisé ne peut proposer à ce tarif : une maîtrise totale de votre environnement, des performances optimales grâce à Redis, une sécurité proactive avec CrowdSec, une exposition sécurisée via Cloudflare Tunnel, et des sauvegardes chiffrées automatiques avec Duplicati. Pour aller encore plus loin, explorez Nginx Proxy Manager pour héberger plusieurs sites sur le même serveur avec une interface visuelle. Et si vous souhaitez automatiser vos tâches WordPress, découvrez notre guide sur n8n + WordPress pour gagner 10h par semaine.

Des questions ou des blocages ? Laissez un commentaire ci-dessous. Bonne installation !

Articles similaires

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *