Essayez sans attendre l'hébergement proposé par WordPress
-15% sur le premier mois avec le code 2025PRESS15AFF

Essayer maintenant

Déployer WordPress avec GitHub Actions et WP-CLI : Pipeline CI/CD complet de zéro à la production

Déployer WordPress en production, c’est souvent le moment où tout peut partir en vrille : un fichier écrasé, une mise à jour ratée, et c’est la panique. Pourtant, avec un pipeline CI/CD bien configuré, on peut automatiser tout ça proprement — du push GitHub jusqu’au serveur — sans jamais croiser les doigts. Dans cet article, on va construire ensemble un workflow complet avec GitHub Actions et WP-CLI, de zéro à la production.

Préparer l’environnement : dépôt Git, structure du projet et WP-CLI

Avant d’écrire la moindre ligne de YAML, il faut poser des bases solides. Un pipeline CI/CD mal construit sur un projet mal structuré, c’est la recette assurée pour les mauvaises surprises en production. Donc on prend le temps de bien faire les choses dès le départ.

Organiser son dépôt GitHub pour un projet WordPress

La première question à se poser : qu’est-ce qu’on versionne exactement ? WordPress lui-même n’a pas vraiment besoin d’être dans votre dépôt (il sera installé via WP-CLI). Par contre, votre thème, vos plugins maison et votre configuration de déploiement, oui.

Un .gitignore adapté à WordPress est absolument indispensable. Voici les règles de base à inclure :

# WordPress core
/wp-admin/
/wp-includes/

# Configuration sensible
wp-config.php

# Uploads générés par les utilisateurs
wp-content/uploads/

# Cache et fichiers temporaires
wp-content/cache/
wp-content/upgrade/

# Dépendances (si vous utilisez Composer)
/vendor/

L’idée, c’est de ne versionner que ce que vous produisez : votre thème enfant, vos plugins custom, vos fichiers de configuration de déploiement. Le reste (core WordPress, uploads, cache) n’a rien à faire dans Git.

Une structure de dépôt propre ressemble souvent à ça :

mon-projet-wp/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── wp-content/
│   ├── themes/
│   │   └── mon-theme/
│   └── plugins/
│       └── mon-plugin/
├── .gitignore
└── README.md

Simple, lisible, maintenable. Et wp-config.php n’apparaît nulle part — on y reviendra.

Installer et configurer WP-CLI en local et sur le serveur

WP-CLI, c’est l’outil qui va rendre votre pipeline vraiment puissant. Si vous ne l’utilisez pas encore, vous allez vite comprendre pourquoi il est indispensable dans une approche CI/CD.

L’installation en local est rapide :

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

Vérifiez ensuite que tout fonctionne avec :

wp --info

Vous devriez voir s’afficher la version de WP-CLI, la version de PHP utilisée, et quelques infos système. Si c’est le cas, vous êtes prêts.

Côté serveur distant, la procédure est identique (la plupart des hébergeurs VPS ou dédiés sous Linux l’acceptent sans problème). L’intérêt ? Votre pipeline GitHub Actions pourra se connecter en SSH et lancer des commandes wp directement sur le serveur : mise à jour des plugins, flush du cache, migrations de base de données… tout ça sans interface graphique.

Un exemple concret de commande distante via SSH :

ssh user@votre-serveur "cd /var/www/mon-site && wp core update --allow-root"

Pratique, non ?

Les secrets GitHub Actions : stocker les credentials sans risque

C’est probablement la partie la plus critique de toute cette configuration. Ne jamais, au grand jamais, committer des credentials dans votre dépôt Git. Pas de mot de passe en dur, pas de clé SSH, pas de wp-config.php avec vos informations de base de données.

GitHub Actions propose un système de secrets chiffré, accessible depuis Settings > Secrets and variables > Actions dans votre dépôt. Voici les secrets typiques à configurer pour un déploiement WordPress :

Nom du secretContenu
SSH_HOSTL’adresse IP ou domaine de votre serveur
SSH_USERL’utilisateur SSH (ex: deploy ou ubuntu)
SSH_PRIVATE_KEYVotre clé privée SSH (contenu complet du fichier)
DB_PASSWORDLe mot de passe de la base de données
DB_NAMELe nom de la base de données

Ces secrets sont ensuite disponibles dans vos workflows via la syntaxe ${{ secrets.SSH_HOST }}. GitHub les masque automatiquement dans les logs — vous ne verrez jamais leur valeur en clair.

Et pour wp-config.php ? On le génère dynamiquement lors du déploiement à partir de ces variables d’environnement, ou on le place directement sur le serveur (hors du dépôt Git) en amont. Les deux approches fonctionnent ; la seconde est souvent plus simple à mettre en place pour commencer.

Construire le pipeline GitHub Actions étape par étape

On a posé les bases dans la section précédente : dépôt structuré, WP-CLI opérationnel, secrets configurés. Maintenant, on passe aux choses sérieuses. Le fichier .github/workflows/deploy.yml va centraliser toute la logique de déploiement — du push Git jusqu’à la mise en production. On le construit bloc par bloc.

Déclencher le workflow : push, pull_request et environnements

La section on du workflow, c’est le point d’entrée. C’est elle qui définit quand GitHub Actions doit s’exécuter.

on:
  push:
    branches:
      - main       # Déploiement en production
      - develop    # Déploiement en staging
  pull_request:
    branches:
      - main       # Lancement des tests uniquement (pas de déploiement)

L’idée est simple : un push sur develop déclenche le déploiement vers le serveur de staging, un push sur main va en production. Pour les pull requests, on exécute uniquement les jobs de lint et de tests — sans jamais toucher au serveur. C’est une bonne pratique de sécurité.

Et pour différencier l’environnement cible selon la branche, on utilise une condition dans le job de déploiement :

environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

Ça permet aussi de gérer des secrets distincts par environnement dans les paramètres GitHub.

Le job de build : install des dépendances, lint et tests

Ce job s’occupe de tout ce qui est vérification et compilation avant de toucher au serveur. Voici un exemple commenté :

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Récupération du code source
      - name: Checkout
        uses: actions/checkout@v4

      # Setup Node.js pour compiler les assets (SCSS, JS, etc.)
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install JS dependencies
        run: npm ci

      - name: Build assets
        run: npm run build

      # Lint PHP : détection des erreurs de syntaxe
      - name: PHP Lint
        run: find . -name "*.php" -not -path "./vendor/*" | xargs php -l

      # (Optionnel) PHPCS pour les standards de code
      - name: PHP CodeSniffer
        run: vendor/bin/phpcs --standard=WordPress wp-content/themes/mon-theme/

Le npm ci est préférable à npm install en CI : il installe exactement ce qui est dans le package-lock.json, sans surprise. Et le lint PHP avec php -l coûte pratiquement rien en temps d’exécution — aucune raison de s’en priver.

Le job de déploiement SSH avec rsync et WP-CLI

C’est le cœur du pipeline. Ce job ne s’exécute qu’après le succès du job build (grâce à needs: build) et uniquement sur les branches main ou develop.

  deploy:
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      # Reconstruction des assets pour le déploiement
      - name: Setup Node & Build
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci && npm run build

      # Ajout de la clé SSH privée (stockée en secret)
      - name: Setup SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts

      # Transfert des fichiers avec rsync
      - name: Deploy via rsync
        run: |
          rsync -avz --delete \
            --exclude '.git' \
            --exclude '.github' \
            --exclude 'node_modules' \
            --exclude 'wp-config.php' \
            --exclude 'wp-content/uploads/' \
            --exclude '.env' \
            -e "ssh -i ~/.ssh/id_rsa -p ${{ secrets.SSH_PORT }}" \
            ./ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.DEPLOY_PATH }}/

      # Commandes WP-CLI post-déploiement
      - name: Post-deploy WP-CLI
        run: |
          ssh -i ~/.ssh/id_rsa -p ${{ secrets.SSH_PORT }} \
            ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
            cd ${{ secrets.DEPLOY_PATH }}
            wp cache flush
            wp core update-db
            wp rewrite flush
          EOF

Les flags rsync sont importants : --exclude wp-config.php pour ne jamais écraser la config du serveur, --exclude wp-content/uploads/ pour ne pas transférer des dizaines de Go de médias à chaque déploiement. Le --delete supprime les fichiers absents du dépôt côté serveur — pratique pour nettoyer les anciens plugins ou thèmes désactivés.

Sur le wp plugin update --all : je le déconseille en déploiement automatique. Mettre à jour des plugins sans test préalable en production, c’est risqué. Réservez cette commande aux environnements de staging ou aux mises à jour manuelles validées.

Gérer les migrations de base de données et les mises à jour automatiques

La base de données, c’est souvent le point de friction dans un pipeline CI/CD WordPress. Avant chaque déploiement, une sauvegarde est indispensable :

      # Sauvegarde DB avant déploiement
      - name: Backup database
        run: |
          ssh -i ~/.ssh/id_rsa -p ${{ secrets.SSH_PORT }} \
            ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
            "cd ${{ secrets.DEPLOY_PATH }} && wp db export backups/pre-deploy-$(date +%Y%m%d-%H%M%S).sql"

Pour les changements d’URL entre staging et production, wp search-replace est votre meilleur allié :

      # Search-replace pour staging → prod (uniquement sur la branche main)
      - name: Search-replace staging URLs
        if: github.ref == 'refs/heads/main'
        run: |
          ssh -i ~/.ssh/id_rsa -p ${{ secrets.SSH_PORT }} \
            ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} \
            "cd ${{ secrets.DEPLOY_PATH }} && wp search-replace 'https://staging.monsite.com' 'https://monsite.com' --skip-columns=guid"

Attention au flag --skip-columns=guid : il évite de réécrire les GUIDs des posts, ce qui peut casser la synchronisation RSS et d’autres fonctionnalités.

Pour du zero-downtime, deux approches sont possibles :

  • Mode maintenance : activer wp maintenance-mode activate avant le rsync, puis wp maintenance-mode deactivate après. Simple mais l’utilisateur voit une page d’attente.
  • Stratégie symlink : déployer dans un dossier horodaté (releases/20240115-143000/), puis basculer le symlink current vers ce nouveau dossier atomiquement. Plus complexe à mettre en place, mais vraiment transparent pour les visiteurs.

Pour la plupart des projets WordPress, le mode maintenance suffira largement. La stratégie symlink est plutôt adaptée aux sites à fort trafic qui ne peuvent pas se permettre la moindre interruption.

Bonnes pratiques, debug et aller plus loin

On a maintenant un pipeline fonctionnel, c’est bien. Mais un bon pipeline, c’est aussi un pipeline qu’on surveille et qu’on sait déboguer quand quelque chose part en vrille à 23h un vendredi soir. Croyez-moi, ça arrive.

Surveiller les déploiements : notifications Slack et badges de statut

La première chose que j’ai faite après avoir mis en prod mon premier pipeline, c’est ajouter des notifications Slack. Pas par gadget — par nécessité. Savoir en temps réel si un déploiement a réussi ou échoué, ça change tout.

L’option la plus simple : le webhook natif Slack. Vous créez une application Slack, vous récupérez l’URL du webhook, vous la stockez dans vos secrets GitHub (SLACK_WEBHOOK_URL), et vous ajoutez une étape en fin de job :

- name: Notification Slack
  if: always()
  uses: rtCamp/action-slack-notify@v2
  env:
    SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
    SLACK_MESSAGE: "Déploiement ${{ job.status }} sur ${{ github.ref_name }}"
    SLACK_COLOR: ${{ job.status == 'success' && 'good' || 'danger' }}

Le if: always() est important : on veut être notifié même en cas d’échec, surtout en cas d’échec.

Et pour le badge de statut dans votre README.md, rien de plus simple :

![Deploy](https://github.com/{user}/{repo}/actions/workflows/deploy.yml/badge.svg)

Ce badge affiche en temps réel l’état du dernier workflow. C’est un détail visuel, mais il donne une indication rapide de la santé du projet à quiconque ouvre le dépôt.

Erreurs fréquentes et comment les diagnostiquer

Bon, parlons franchement : les premières fois, ça plante. Voici les erreurs que j’ai rencontrées (et que vous rencontrerez probablement aussi) :

Permissions SSH incorrectes

C’est l’erreur numéro un. Si votre clé privée n’a pas les bonnes permissions, SSH refuse tout simplement de l’utiliser. La solution : vérifiez que votre action applique bien un chmod 600 sur la clé avant de s’en servir. Certaines actions SSH le font automatiquement, d’autres non — lisez la doc.

Timeouts rsync sur les médias

Si votre dossier wp-content/uploads pèse plusieurs gigaoctets, rsync va souffrir. La solution évidente : exclure les uploads du sync (ce doit déjà être dans vos flags --exclude). Les médias ne vivent pas dans Git, on en a parlé en section 1.

Conflits de version PHP

Votre environnement local tourne en PHP 8.2, votre serveur en 7.4 ? Ça peut passer… ou pas. Le lint PHP du job de build va attraper les incompatibilités syntaxiques, mais les problèmes de dépendances Composer peuvent être plus sournois. Uniformisez vos environnements, ou utilisez Docker pour le build (j’y reviens juste après).

WP-CLI introuvable dans le PATH distant

Classique. Vous avez installé WP-CLI sur le serveur, mais quand GitHub Actions s’y connecte via SSH, le PATH n’est pas le même qu’en session interactive. Deux options : utiliser le chemin absolu (/usr/local/bin/wp) dans vos commandes, ou ajouter un export PATH en début de script distant.

- name: WP-CLI post-deploy
  run: |
    ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "
      export PATH=$PATH:/usr/local/bin &&
      wp --path=/var/www/html cache flush --allow-root
    "

Pour aller plus loin, quelques pistes concrètes :

  • Multi-environnements : utilisez des jobs conditionnels (if: github.ref == 'refs/heads/main') pour déployer sur staging depuis develop et en prod depuis main. Les secrets GitHub supportent les environnements nativement depuis peu.
  • Docker pour le build : un container avec la bonne version de PHP, Node et Composer garantit un environnement de build reproductible, peu importe le runner GitHub.
  • Tests automatisés : intégrez PHPUnit pour les tests unitaires ou Playwright pour les tests E2E avant le déploiement. Un test qui plante bloque le pipeline — et c’est exactement ce qu’on veut.

Honnêtement ? Mettre en place ce type de pipeline m’a demandé quelques heures bien chargées et quelques cheveux blancs. Mais aujourd’hui, déployer une mise à jour de thème ou un nouveau plugin se résume à un git push. Pas de connexion FTP, pas de « j’espère que j’ai pas oublié un fichier », pas de stress. Juste un push, une notification Slack verte, et c’est réglé. C’est ça, la vraie valeur d’un pipeline CI/CD bien construit.