Si vous avez déjà vu un déploiement WordPress “réussi” casser le site à cause d’un plugin mis à jour au mauvais moment, vous savez pourquoi le CI/CD n’est pas un luxe. La bonne nouvelle : avec GitHub Actions, SSH et un peu de discipline sur la structure du dépôt, vous pouvez rendre vos déploiements répétables, auditables et réversibles.
Ce qu’on va construire
Vous allez mettre en place une chaîne CI/CD GitHub Actions qui :
- Construit un paquet “déployable” (thème(s), plugin(s) maison, mu-plugins, fichiers de config) à partir de votre dépôt Git.
- Déploie automatiquement sur un serveur (VPS, VM, dédié) via SSH + rsync, sans écraser
wp-content/uploadsni toucher àwp-config.php. - Exécute des tâches post-déploiement avec WP-CLI (flush de cache, mises à jour de DB si nécessaire, régénération d’index, etc.).
- Permet un rollback en remettant la release précédente.
Public visé : blogueurs WordPress avancés et équipes techniques légères (1–3 personnes) qui gèrent un site WordPress 6.9.4 (avril 2026) et qui veulent des déploiements propres, sans “FTP à la main”.
À la fin, vous saurez :
- structurer un dépôt Git “WordPress-friendly” sans versionner le core ;
- sécuriser des secrets GitHub Actions (clé SSH, host, chemin) ;
- déployer en environnement de staging puis production avec des garde-fous ;
- diagnostiquer les erreurs classiques (permissions, rsync, WP-CLI, caches).
Résumé rapide
- Vous versionnez uniquement ce qui vous appartient :
wp-content/themes,wp-content/plugins(custom),mu-plugins, scripts de déploiement. - GitHub Actions construit un artefact et le pousse via rsync over SSH vers un dossier
releases/. - Vous basculez un symlink
current(rollback facile) ou vous synchronisez directementwp-content(plus simple). - WP-CLI exécute les tâches post-déploiement : caches, permaliens, actions spécifiques.
- Vous excluez strictement
uploads,cache, logs, et tout secret. - Vous testez d’abord en staging, avec un workflow identique.
Quand utiliser cette solution
- Vous avez un serveur auquel vous pouvez accéder en SSH (même mutualisé “premium” si SSH + rsync sont disponibles).
- Vous voulez tracer qui a déployé quoi (commit SHA), et pouvoir revenir en arrière en 30 secondes.
- Votre site a des composants versionnables : thème enfant, plugins maison, mu-plugins, snippets “propres”.
- Vous utilisez Divi 5 / Elementor / Avada : ça ne change rien au pipeline, tant que vous ne versionnez pas les caches générés.
Quand ne PAS utiliser cette solution
- Vous êtes sur un mutualisé sans SSH, sans rsync, sans accès à WP-CLI. Dans ce cas, regardez la variante “plugin de déploiement” ou migrez l’hébergement.
- Vous déployez un WordPress “complet” (core + wp-config) sans séparation : vous finirez par versionner des secrets ou casser des environnements.
- Vous n’avez pas de staging. Déployer automatiquement sur production sans filet, je l’ai vu finir en incident à cause d’un simple
fatal errorPHP 8.1.
Avant de commencer (prérequis)
Versions et contraintes (avril 2026)
- WordPress : 6.9.4 (cible). Les commandes WP-CLI et la structure
wp-contentrestent stables. - PHP : 8.1 minimum recommandé. Vérifiez aussi la version PHP côté CI si vous lancez des tests.
- Serveur : SSH + rsync + un utilisateur non-root (recommandé) + accès en écriture au dossier du site.
- WP-CLI installé côté serveur (ou au minimum téléchargeable). Doc officielle : wp-cli.org.
Sauvegarde et environnement
- Créez un staging (sous-domaine ou VM). Même pipeline, variables différentes.
- Avant le premier déploiement : sauvegarde fichiers + base de données. Si vous avez un hébergeur managé, déclenchez un snapshot.
- Assurez-vous de pouvoir revenir en arrière : soit via release précédente, soit via snapshot.
Sécurité (à ne pas rater)
- Ne mettez jamais
wp-config.phpdans Git. Jamais. - La clé SSH utilisée par GitHub Actions doit être limitée (utilisateur dédié, droits minimaux).
- Évitez d’exécuter des commandes WP-CLI arbitraires venant de PR non approuvées (risque RCE via workflow). Utilisez des protections d’environnement GitHub.
Sources officielles utiles
- Developer Resources (WordPress)
- Documentation WordPress
- WordPress Core Trac (suivi des changements)
- Dépôt GitHub wordpress-develop
- Manuel PHP (8.1+)
Étape 1 : Structurer le dépôt Git pour un WordPress déployable
Le problème vient souvent d’un dépôt Git “fourre-tout” : core WordPress versionné, uploads dans Git, caches de builder commités… et chaque déploiement devient imprévisible. Le but ici : versionner ce que vous maîtrisez et laisser le reste au serveur.
1.1 Arborescence recommandée
À la racine de votre dépôt :
# Exemple d'arborescence
.
├─ wp-content/
│ ├─ mu-plugins/
│ │ └─ bpcab-deploy-hooks.php
│ ├─ plugins/
│ │ └─ mon-plugin-custom/
│ └─ themes/
│ └─ mon-theme-enfant/
├─ deploy/
│ ├─ rsync-excludes.txt
│ └─ post-deploy.sh
├─ .github/
│ └─ workflows/
│ └─ deploy.yml
└─ composer.json (optionnel)
Vous ne commitez pas :
wp-admin,wp-includes(le core)wp-content/uploadswp-content/cacheet assimiléswp-config.php
1.2 Ajouter un fichier d’exclusions rsync
Créez deploy/rsync-excludes.txt :
# Fichiers/dossiers à ne jamais pousser
wp-content/uploads/
wp-content/cache/
wp-content/upgrade/
wp-content/backups/
wp-content/wflogs/
wp-content/ai1wm-backups/
wp-content/debug.log
# Artefacts OS/IDE
.DS_Store
Thumbs.db
.vscode/
.idea/
# Git
.git/
.github/
Dans mon expérience, l’oubli de uploads/ est la cause n°1 des déploiements “qui prennent 45 minutes” et saturent le serveur.
1.3 (Optionnel) mu-plugin de hooks de déploiement
Créez wp-content/mu-plugins/bpcab-deploy-hooks.php pour centraliser des actions post-déploiement (sans dépendre du thème). Les mu-plugins sont chargés tôt et ne peuvent pas être désactivés par erreur.
<?php
/**
* Plugin Name: BPCAB Deploy Hooks
* Description: Hooks utilitaires déclenchés après déploiement (WP 6.9.4+, PHP 8.1+).
*/
declare(strict_types=1);
if (!defined('ABSPATH')) {
exit;
}
/**
* Exemple : ajouter un endpoint de healthcheck interne.
* Pratique pour vérifier qu'une release répond sans exposer d'infos sensibles.
*/
add_action('rest_api_init', function (): void {
register_rest_route('bpcab/v1', '/health', [
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function () {
return new WP_REST_Response([
'status' => 'ok',
'wp' => get_bloginfo('version'),
'php' => PHP_VERSION,
'time' => time(),
], 200);
},
]);
}, 10);
Résultat attendu : en staging, /wp-json/bpcab/v1/health retourne un JSON simple.
Étape 2 : Préparer l’accès serveur (SSH, clés, permissions)
Vous allez donner à GitHub Actions un accès SSH. Le piège classique : utiliser la clé perso de l’admin. Ne faites pas ça. Créez un utilisateur dédié, limité au dossier du site.
2.1 Créer un utilisateur “deploy” (VPS/dédié)
Sur le serveur :
# À exécuter en root ou via sudo
sudo adduser deploy
sudo usermod -aG www-data deploy
Adaptez le groupe web (www-data, nginx, apache) selon votre OS.
2.2 Préparer les dossiers releases
Supposons que votre site vit dans /var/www/mon-site et que le document root pointe vers /var/www/mon-site/current.
sudo mkdir -p /var/www/mon-site/releases
sudo mkdir -p /var/www/mon-site/shared/wp-content/uploads
sudo mkdir -p /var/www/mon-site/shared/wp-content/cache
# Permissions (à adapter à votre stack)
sudo chown -R deploy:www-data /var/www/mon-site
sudo find /var/www/mon-site -type d -exec chmod 775 {} ;
sudo find /var/www/mon-site -type f -exec chmod 664 {} ;
Oui, les permissions sont un sujet pénible. Mais si rsync n’a pas le droit d’écrire, vous aurez des “silent failures” (ou un site partiellement mis à jour).
2.3 Générer une clé SSH pour GitHub Actions
Sur votre machine locale :
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./github_actions_deploy_key
Ajoutez la clé publique sur le serveur :
# Sur le serveur, en utilisateur deploy
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Collez le contenu de github_actions_deploy_key.pub.
2.4 Ajouter les secrets dans GitHub
Dans GitHub : Settings → Secrets and variables → Actions.
- SSH_PRIVATE_KEY : contenu de
github_actions_deploy_key(la clé privée) - SSH_HOST : IP ou hostname
- SSH_USER :
deploy - SSH_PORT :
22(ou autre) - DEPLOY_PATH :
/var/www/mon-site - WP_CLI_PATH : ex
/usr/local/bin/wp(selon installation)
Pour la prod, utilisez idéalement Environments (staging/prod) avec approbation manuelle. Doc officielle : GitHub Environments.
Étape 3 : Créer le workflow GitHub Actions (build + artefacts)
On va construire un artefact “propre” puis le déployer. Le build inclut typiquement : install Composer (si vous en avez), build assets (si vous avez un bundler), puis création d’un tarball.
3.1 Créer .github/workflows/deploy.yml
name: Deploy WordPress wp-content
on:
push:
branches:
- main
workflow_dispatch:
concurrency:
group: deploy-main
cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Préparer PHP (pour outils éventuels)
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2
- name: Installer dépendances Composer (optionnel)
run: |
set -euo pipefail
if [ -f composer.json ]; then
composer install --no-dev --prefer-dist --no-interaction --no-progress
else
echo "Pas de composer.json, étape ignorée."
fi
- name: Créer l'artefact de déploiement
run: |
set -euo pipefail
# On ne déploie que ce qu'on veut : wp-content + scripts nécessaires
mkdir -p build
rsync -a --delete
--exclude-from="deploy/rsync-excludes.txt"
wp-content/ build/wp-content/
# On embarque les scripts de déploiement (côté runner)
mkdir -p build/deploy
cp -a deploy/ build/deploy/
# Archive pour transfert plus rapide (optionnel, mais pratique)
tar -czf artifact.tar.gz -C build .
- name: Ajouter la clé SSH
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Ajouter le serveur aux known_hosts
run: |
set -euo pipefail
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${{ secrets.SSH_PORT }}" -H "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts
- name: Déployer l'artefact sur le serveur
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_PORT: ${{ secrets.SSH_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
run: |
set -euo pipefail
RELEASE_ID="$(date +%Y%m%d%H%M%S)-${GITHUB_SHA::7}"
echo "Release: ${RELEASE_ID}"
# 1) Envoyer l'artefact
scp -P "${SSH_PORT}" artifact.tar.gz "${SSH_USER}@${SSH_HOST}:/tmp/artifact-${RELEASE_ID}.tar.gz"
# 2) Déployer côté serveur : extraire dans releases/RELEASE_ID
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
RELEASE_DIR="${DEPLOY_PATH}/releases/${RELEASE_ID}"
mkdir -p "${RELEASE_DIR}"
tar -xzf "/tmp/artifact-${RELEASE_ID}.tar.gz" -C "${RELEASE_DIR}"
rm -f "/tmp/artifact-${RELEASE_ID}.tar.gz"
# Lier uploads partagés (si votre docroot est current/)
mkdir -p "${DEPLOY_PATH}/shared/wp-content/uploads"
rm -rf "${RELEASE_DIR}/wp-content/uploads"
ln -s "${DEPLOY_PATH}/shared/wp-content/uploads" "${RELEASE_DIR}/wp-content/uploads"
# Lier cache partagé si vous en avez besoin
mkdir -p "${DEPLOY_PATH}/shared/wp-content/cache"
rm -rf "${RELEASE_DIR}/wp-content/cache"
ln -s "${DEPLOY_PATH}/shared/wp-content/cache" "${RELEASE_DIR}/wp-content/cache"
# Basculer current (symlink) : rollback facile
ln -sfn "${RELEASE_DIR}" "${DEPLOY_PATH}/current"
'"
- name: Post-déploiement (WP-CLI)
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_PORT: ${{ secrets.SSH_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
WP_CLI_PATH: ${{ secrets.WP_CLI_PATH }}
run: |
set -euo pipefail
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
cd "${DEPLOY_PATH}/current"
# Remplacez --path si votre WordPress n'est pas dans current/
WP="${WP_CLI_PATH}"
# Vérifier que WP-CLI voit bien l'installation
"${WP}" core version
# Exemple : vider les caches WordPress (transients)
"${WP}" transient delete --all || true
# Exemple : régénérer les règles de réécriture (si vous touchez aux routes)
"${WP}" rewrite flush --hard || true
'"
3.2 Pourquoi cette architecture “releases + current”
- Vous évitez l’état “mi-déployé” si rsync est interrompu.
- Le rollback consiste à repointer
currentvers la release précédente. - Vous pouvez garder N releases pour audit.
Edge case réel : si vous utilisez OPcache agressif + FPM, un basculement de symlink peut laisser des scripts en cache. Prévoyez un reload PHP-FPM si nécessaire (voir section maintenance).
Étape 4 : Déployer via rsync (zéro-downtime “pragmatique”)
La méthode tar+extract marche très bien. Mais rsync a deux avantages : diff incrémental et logs plus clairs. Ici, je vous montre une variante où l’artefact est déjà prêt et vous rsync vers la release.
4.1 Script serveur : deploy/post-deploy.sh
Ce script sera copié dans l’artefact. Il s’exécute côté serveur. Créez deploy/post-deploy.sh :
#!/usr/bin/env bash
# Script post-déploiement côté serveur (à adapter)
set -euo pipefail
DEPLOY_PATH="${1:-}"
RELEASE_ID="${2:-}"
WP_CLI_PATH="${3:-/usr/local/bin/wp}"
if [ -z "${DEPLOY_PATH}" ] || [ -z "${RELEASE_ID}" ]; then
echo "Usage: post-deploy.sh /chemin/deploy RELEASE_ID /chemin/wp"
exit 1
fi
cd "${DEPLOY_PATH}/current"
# Vérifier WP-CLI
"${WP_CLI_PATH}" core version
# Vider transients (évite des incohérences après changements d'options)
"${WP_CLI_PATH}" transient delete --all || true
# Flush rewrite si vous avez modifié CPT, endpoints, etc.
"${WP_CLI_PATH}" rewrite flush --hard || true
# Exemple : si vous utilisez un plugin de cache, vous pouvez déclencher un purge via WP-CLI si disponible
# "${WP_CLI_PATH}" cache flush || true
echo "Post-déploiement terminé pour ${RELEASE_ID}"
N’oubliez pas de rendre le script exécutable dans Git :
chmod +x deploy/post-deploy.sh
4.2 Ajuster le workflow pour rsync (option)
Remplacez l’étape “Déployer l’artefact” par :
# Exemple d'étape run (à intégrer dans votre YAML)
set -euo pipefail
RELEASE_ID="$(date +%Y%m%d%H%M%S)-${GITHUB_SHA::7}"
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
mkdir -p "${DEPLOY_PATH}/releases/${RELEASE_ID}"
'"
# Rsync vers la release (plus lisible que tar pour certains)
rsync -az --delete
-e "ssh -p ${SSH_PORT}"
--exclude-from="deploy/rsync-excludes.txt"
build/ "${SSH_USER}@${SSH_HOST}:${DEPLOY_PATH}/releases/${RELEASE_ID}/"
# Symlinks et bascule current
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
RELEASE_DIR="${DEPLOY_PATH}/releases/${RELEASE_ID}"
mkdir -p "${DEPLOY_PATH}/shared/wp-content/uploads"
rm -rf "${RELEASE_DIR}/wp-content/uploads"
ln -s "${DEPLOY_PATH}/shared/wp-content/uploads" "${RELEASE_DIR}/wp-content/uploads"
ln -sfn "${RELEASE_DIR}" "${DEPLOY_PATH}/current"
'"
Résultat attendu : un dossier releases/... avec votre wp-content et un current qui pointe dessus.
Étape 5 : Lancer les tâches post-déploiement avec WP-CLI (migrations, caches)
Le point délicat : WordPress n’a pas de “migrations” natives comme Laravel. Les plugins, eux, font souvent des upgrades au chargement admin. En CI/CD, vous voulez éviter que la première visite admin déclenche une migration longue.
5.1 Déclencher des upgrades “safe”
Ce que vous pouvez faire de façon raisonnable :
- Vérifier la version (sanity check).
- Flush rewrite si vous avez touché à des routes.
- Purger caches (transients, caches plugin si WP-CLI le supporte).
- Exécuter vos propres migrations (dans un mu-plugin) via une commande WP-CLI custom.
5.2 Ajouter une commande WP-CLI custom (mu-plugin)
Dans wp-content/mu-plugins/bpcab-deploy-cli.php :
<?php
/**
* Plugin Name: BPCAB Deploy CLI
* Description: Commandes WP-CLI de déploiement (WP 6.9.4+, PHP 8.1+).
*/
declare(strict_types=1);
if (!defined('ABSPATH')) {
exit;
}
if (defined('WP_CLI') && WP_CLI) {
/**
* Commande: wp bpcab deploy
* Exemple: wp bpcab deploy --allow-root
*/
WP_CLI::add_command('bpcab deploy', function (array $args, array $assoc_args): void {
// Exemple : “migration” maison basée sur une option de version.
$current = (string) get_option('bpcab_schema_version', '0');
$target = '1';
if (version_compare($current, $target, '>=')) {
WP_CLI::log('Schéma déjà à jour.');
return;
}
// Exemple concret : créer une option/structure requise par une nouvelle release.
// Remplacez par vos vrais besoins (création de tables, backfill, etc.).
update_option('bpcab_feature_flag_new_header', '1', false);
update_option('bpcab_schema_version', $target, false);
WP_CLI::success('Migration appliquée, schéma = ' . $target);
});
}
Puis, dans le workflow, remplacez/ajoutez :
"${WP}" bpcab deploy || true
Pourquoi || true ? Sur certains sites, je préfère que le déploiement n’échoue pas pour une migration “non critique”. Pour une migration critique, retirez-le et faites échouer le job.
Étape 6 : Ajouter un rollback simple et fiable
Un rollback efficace, c’est 80% de la valeur d’un pipeline. Le plus simple : garder les releases et repointer current.
6.1 Garder les N dernières releases
Ajoutez une étape serveur après le switch :
# À exécuter côté serveur
cd /var/www/mon-site/releases
ls -1dt */ | tail -n +6 | xargs -r rm -rf
Ici on garde 5 releases. Ajustez selon l’espace disque.
6.2 Workflow GitHub “Rollback” (manuel)
Ajoutez un second workflow .github/workflows/rollback.yml :
name: Rollback WordPress release
on:
workflow_dispatch:
inputs:
release:
description: "Nom du dossier release (ex: 20260413121000-abc1234)"
required: true
type: string
jobs:
rollback:
runs-on: ubuntu-latest
steps:
- name: Ajouter la clé SSH
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: known_hosts
run: |
set -euo pipefail
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${{ secrets.SSH_PORT }}" -H "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts
- name: Basculer current vers la release demandée
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_PORT: ${{ secrets.SSH_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
RELEASE: ${{ inputs.release }}
run: |
set -euo pipefail
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
TARGET="${DEPLOY_PATH}/releases/${RELEASE}"
if [ ! -d "${TARGET}" ]; then
echo "Release introuvable: ${TARGET}"
exit 1
fi
ln -sfn "${TARGET}" "${DEPLOY_PATH}/current"
echo "Rollback effectué vers ${RELEASE}"
'"
Résultat attendu : rollback déclenché depuis GitHub UI, sans toucher à la base de données.
Le résultat complet
Si vous voulez tout copier d’un coup, voici le minimum viable : exclusions, workflow deploy, et un post-deploy WP-CLI.
Fichier deploy/rsync-excludes.txt
wp-content/uploads/
wp-content/cache/
wp-content/upgrade/
wp-content/backups/
wp-content/wflogs/
wp-content/ai1wm-backups/
wp-content/debug.log
.DS_Store
Thumbs.db
.vscode/
.idea/
.git/
.github/
Fichier deploy/post-deploy.sh
#!/usr/bin/env bash
# Script post-déploiement côté serveur
set -euo pipefail
DEPLOY_PATH="${1:-}"
RELEASE_ID="${2:-}"
WP_CLI_PATH="${3:-/usr/local/bin/wp}"
if [ -z "${DEPLOY_PATH}" ] || [ -z "${RELEASE_ID}" ]; then
echo "Usage: post-deploy.sh /chemin/deploy RELEASE_ID /chemin/wp"
exit 1
fi
cd "${DEPLOY_PATH}/current"
"${WP_CLI_PATH}" core version
"${WP_CLI_PATH}" transient delete --all || true
"${WP_CLI_PATH}" rewrite flush --hard || true
echo "OK post-déploiement ${RELEASE_ID}"
Fichier .github/workflows/deploy.yml
name: Deploy WordPress wp-content
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-main
cancel-in-progress: true
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Préparer PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2
- name: Build (Composer optionnel)
run: |
set -euo pipefail
if [ -f composer.json ]; then
composer install --no-dev --prefer-dist --no-interaction --no-progress
fi
- name: Préparer build/
run: |
set -euo pipefail
mkdir -p build
rsync -a --delete --exclude-from="deploy/rsync-excludes.txt" wp-content/ build/wp-content/
mkdir -p build/deploy
cp -a deploy/ build/deploy/
tar -czf artifact.tar.gz -C build .
- name: SSH agent
uses: webfactory/[email protected]
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: known_hosts
run: |
set -euo pipefail
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keyscan -p "${{ secrets.SSH_PORT }}" -H "${{ secrets.SSH_HOST }}" >> ~/.ssh/known_hosts
- name: Déployer
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_PORT: ${{ secrets.SSH_PORT }}
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
WP_CLI_PATH: ${{ secrets.WP_CLI_PATH }}
run: |
set -euo pipefail
RELEASE_ID="$(date +%Y%m%d%H%M%S)-${GITHUB_SHA::7}"
scp -P "${SSH_PORT}" artifact.tar.gz "${SSH_USER}@${SSH_HOST}:/tmp/artifact-${RELEASE_ID}.tar.gz"
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" bash -lc "'
set -euo pipefail
RELEASE_DIR="${DEPLOY_PATH}/releases/${RELEASE_ID}"
mkdir -p "${RELEASE_DIR}"
tar -xzf "/tmp/artifact-${RELEASE_ID}.tar.gz" -C "${RELEASE_DIR}"
rm -f "/tmp/artifact-${RELEASE_ID}.tar.gz"
mkdir -p "${DEPLOY_PATH}/shared/wp-content/uploads"
rm -rf "${RELEASE_DIR}/wp-content/uploads"
ln -s "${DEPLOY_PATH}/shared/wp-content/uploads" "${RELEASE_DIR}/wp-content/uploads"
ln -sfn "${RELEASE_DIR}" "${DEPLOY_PATH}/current"
chmod +x "${RELEASE_DIR}/deploy/post-deploy.sh"
"${RELEASE_DIR}/deploy/post-deploy.sh" "${DEPLOY_PATH}" "${RELEASE_ID}" "${WP_CLI_PATH}"
cd "${DEPLOY_PATH}/releases"
ls -1dt */ | tail -n +6 | xargs -r rm -rf
'"
Personnalisation
- Ajoutez un environnement staging avec un autre
DEPLOY_PATHet des secrets séparés. - Si vous avez un build front (Vite/Webpack), lancez-le avant la création de l’artefact et copiez les fichiers buildés dans le thème.
- Si vous utilisez Composer pour des libs PHP dans un plugin, gardez
vendor/dans l’artefact (mais pas forcément dans Git).
Adapter pour Divi 5 / Elementor / Avada
Le CI/CD ne dépend pas du builder. Ce qui change : ce que vous ne devez surtout pas déployer.
Divi 5
- Évitez de versionner/déployer des caches générés (selon votre setup : CSS statique, fichiers dans
wp-content). - Si vous avez un thème enfant Divi, versionnez-le comme n’importe quel thème enfant.
- Après déploiement, si vous observez un front “sans styles”, le problème est souvent un cache (plugin cache/CDN) et pas Divi lui-même.
Elementor
- Elementor stocke beaucoup de choses en DB. Un déploiement de fichiers ne “migre” pas vos templates.
- Si vous déployez un widget custom, ajoutez un
rewrite flushseulement si vous ajoutez des endpoints, sinon évitez (coûteux). - Si vous utilisez un cache agressif, prévoyez un purge ciblé après déploiement.
Avada (Fusion Builder)
- Avada génère aussi des assets/caches. Ne versionnez pas ces dossiers de cache.
- Pour des éléments custom (shortcodes, CPT), placez-les dans un plugin custom versionné plutôt que dans
functions.phpdu thème parent.
Vérification finale
- Dans GitHub, vérifiez que le workflow passe en vert sur
main. - Sur le serveur :
ls -la /var/www/mon-sitedoit montrerreleases/,shared/etcurrent(symlink). - Visitez
/wp-json/bpcab/v1/healthsi vous avez ajouté le mu-plugin : vous devez voirwp: 6.9.4(ou votre version). - Vérifiez que
wp-content/uploadscontient bien vos médias et n’a pas été écrasé. - Ouvrez le front et l’admin. Si vous avez un plugin de cache, purgez-le et retestez.
Si le résultat n’est pas celui attendu
| Symptôme | Cause probable | Vérification | Solution |
|---|---|---|---|
Le workflow échoue sur ssh-keyscan |
Port/host incorrect, firewall | Depuis votre machine : ssh -p PORT user@host |
Corrigez SSH_HOST/SSH_PORT, ouvrez le port, vérifiez DNS |
| Déploiement OK mais site en erreur 500 | Fatal PHP, dépendance manquante, incompatibilité PHP 8.1+ | Logs PHP-FPM/Apache/Nginx, wp-content/debug.log |
Rollback via workflow, corrigez, redeploy ; vérifiez la version PHP serveur |
| Les médias ont disparu | uploads/ écrasé ou symlink absent |
ls -la current/wp-content/uploads |
Recréez shared/uploads et le symlink ; excluez uploads de rsync |
| Les changements CSS n’apparaissent pas | Cache navigateur/CDN/plugin cache | Test en navigation privée + purge cache | Purger cache, versionner les assets avec hash, invalider CDN |
wp core version échoue en CI |
WP-CLI introuvable ou mauvais --path |
SSH puis which wp, wp --info |
Corrigez WP_CLI_PATH ou ajoutez --path=/chemin |
- Copier le code au mauvais endroit : le workflow doit être dans
.github/workflows/, pas ailleurs. - Oublier un point-virgule… : si vous ajoutez un mu-plugin, une erreur PHP casse tout le site. Testez en staging.
- Tester sur production sans sauvegarde : j’ai vu des rollbacks impossibles parce qu’une migration DB s’est déclenchée après le switch. Faites staging d’abord.
Pièges et erreurs courantes
| Erreur | Cause | Solution |
|---|---|---|
Vous déployez wp-config.php par accident |
Dépôt mal structuré | Supprimez-le du dépôt, ajoutez-le aux exclusions, régénérez des secrets si exposés |
| Le site mélange anciens/nouveaux fichiers | Déploiement “in place” interrompu | Utilisez releases/ + symlink current (switch atomique) |
| Permissions incohérentes après rsync | Umask/owner différents | Fixez owner/groupe, évitez de déployer en root, normalisez via chown/chmod |
| Action vs filtre : hook WP mal choisi | Code “snippet” copié d’un vieux tuto | Placez le code dans un plugin, vérifiez le hook sur developer.wordpress.org/reference |
| Assets JS/CSS non chargés | Mauvais wp_enqueue_scripts ou chemins build |
Vérifiez l’enqueue, chemins, et que le build tourne avant l’artefact |
Variante / alternative
Alternative 1 : Déploiement via GitHub Releases + téléchargement serveur
Si votre serveur ne permet pas rsync mais permet de sortir sur Internet, vous pouvez :
- Créer un artefact GitHub (Release)
- Télécharger côté serveur via
curl - Extraire dans
releases/puis switch symlink
C’est moins “propre” (dépend d’un download public/privé, tokens), mais parfois c’est la seule option sur des hébergements bridés.
Alternative 2 : Plateforme managée (si vous pouvez)
Si vous voulez du CI/CD sans gérer SSH, regardez un hébergeur WordPress managé avec pipeline intégré. Je le mentionne parce que le coût humain de la maintenance SSH n’est pas négligeable.
Conseils sécurité, performance et maintenance
- Protégez la production avec GitHub Environments : approbation requise, secrets séparés, logs contrôlés. Doc : Using environments for deployment.
- Limitez la clé SSH : utilisateur dédié, pas de shell si vous pouvez, pas de sudo. Sur OpenSSH, vous pouvez restreindre via options dans
authorized_keys(à manier avec précaution). - Ne loggez jamais les secrets : évitez
set -xdans vos scripts, attention aux echo. - OPcache / PHP-FPM : si vous voyez des comportements “fantômes” après switch, reload FPM peut aider. Ajoutez une commande côté serveur si vous avez les droits (sinon, baissez les TTL OPcache ou utilisez
opcache.validate_timestampsselon votre politique). - Cache plugin/CDN : prévoyez une purge API après déploiement si votre stack le permet. Sinon, vous allez “déboguer” des non-bugs.
Pour aller plus loin
- Ajouter des jobs de tests : lint PHP, PHPCS WordPress, tests JS, build assets.
- Déployer un must-use plugin qui expose la version de release (SHA) dans l’admin pour debug.
- Implémenter un vrai “maintenance mode” pendant la bascule (ou un drain des workers) si vous avez du trafic.
- Ajouter une étape de healthcheck (HTTP 200 + contenu attendu) avant de valider le déploiement.
- Mettre en place un service container pour vos plugins custom (DI) afin de réduire les effets de bord en production.
Ressources
- Documentation GitHub Actions
- Security hardening for GitHub Actions
- WP-CLI
- WordPress Code Reference
- Documentation WordPress
- WordPress Core Trac
- wordpress-develop (GitHub)
- Manuel PHP
FAQ
Est-ce que je dois versionner le core WordPress ?
Non. Versionnez votre code (wp-content) et gérez le core via l’hébergeur, ou via un processus séparé. Versionner le core augmente le risque de conflits et de secrets accidentels.
Pourquoi ne pas déployer la base de données ?
Parce que c’est un autre problème : migrations, données utilisateurs, médias, contenu. Pour un blog, la DB est “vivante”. Déployer la DB à chaque release est rarement souhaitable.
Comment gérer les plugins premium (Divi, Avada, etc.) ?
Évitez de les commiter si la licence l’interdit. Gardez-les installés côté serveur, ou utilisez un dépôt privé si le contrat le permet. Dans tous les cas, ne mélangez pas “code premium” et “code maison” sans vérifier les conditions.
Mon site est sur un mutualisé : c’est mort ?
Pas forcément. Si vous avez SSH + rsync, ça passe. Sinon, regardez la variante “GitHub Release + curl” ou changez d’hébergement.
Dois-je exécuter wp plugin update en post-déploiement ?
Je l’évite en général. Vous voulez que votre déploiement soit déterministe. Mettre à jour des plugins “au moment du déploiement” rend le résultat dépendant de l’état du dépôt WordPress.org à l’instant T.
Que faire si un plugin déclenche une migration DB au premier chargement ?
Ça arrive. Essayez d’identifier une commande WP-CLI du plugin (certains en ont), sinon prévoyez une fenêtre de maintenance et surveillez les logs. Dans le pire cas, rollback fichiers ne suffit pas si la DB a changé.
Comment éviter les surprises de cache après déploiement ?
Purge cache plugin/CDN, incrémentez les versions d’assets (hash), et évitez de versionner des caches générés par Divi/Elementor/Avada.
Pourquoi mon rewrite flush ne prend pas ?
Souvent parce que --path n’est pas bon, ou parce que WP-CLI n’exécute pas dans le bon répertoire. Faites wp --info et forcez --path.
Comment savoir quelle release est en production ?
Le plus simple : exposez le SHA dans un mu-plugin (admin footer, endpoint REST interne). Ou lisez le nom du dossier current côté serveur.
Est-ce compatible avec PHP 8.1+ et WordPress 6.9.4 ?
Oui, le pipeline est agnostique. Le point critique est la compatibilité de vos plugins/thèmes avec PHP 8.1+ ; testez en staging et surveillez les warnings/deperecations.