Aller au contenu principal

WordPress en GitOps avec Bedrock sur un VPS HMS

Ce guide vous explique comment héberger un site WordPress sur un VPS HostMyServers et le gérer en GitOps grâce à Bedrock, le boilerplate WordPress de Roots. Votre dépôt Git devient la source de vérité : le cœur WordPress, les plugins et les thèmes sont déclarés en code, versionnés, puis déployés automatiquement sur votre VPS à chaque git push.

Un VPS vous donne l'accès root, SSH et le contrôle total de la pile (Nginx, PHP-FPM, MariaDB) indispensables à ce mode de déploiement, ce que ne permet pas un hébergement mutualisé.

Pourquoi Bedrock ?

Un WordPress classique mélange le code (cœur, plugins, thèmes), la configuration (wp-config.php) et les données (uploads) dans un même dossier, et se met à jour depuis l'interface d'administration. Il est donc difficile à versionner et à reproduire. Bedrock apporte :

  • Composer pour gérer le cœur WordPress, les plugins et les thèmes comme des dépendances (versions figées dans composer.lock)
  • Une configuration par variables d'environnement (fichier .env), sans secret dans Git
  • Des fichiers de configuration par environnement (développement, staging, production)
  • Une arborescence plus sûre : seul le dossier web/ est exposé par le serveur web
  • La désactivation des modifications depuis l'administration en production (DISALLOW_FILE_MODS)

Commander un VPS HostMyServers

Ce guide est conçu pour un VPS HostMyServers. Choisissez l'offre selon votre trafic :

  • VPS NVMe - Excellent rapport qualité/prix, idéal pour démarrer un site vitrine ou un blog
  • VPS Performance - Recommandé pour les boutiques WooCommerce et les sites à fort trafic
UsagevCPURAMStockage
Site vitrine / blog1-22 Go20 Go NVMe
Site à trafic moyen, plusieurs sites2-44 Go40 Go NVMe
WooCommerce / fort trafic4+8 Go+80 Go NVMe+

Lors de la commande, sélectionnez Ubuntu 24.04 LTS ou Debian 12 comme système d'exploitation. Une fois le VPS livré, l'adresse IP et les identifiants root vous sont communiqués par e-mail et sont disponibles dans votre espace client HostMyServers.

Projets plus importants

Pour plusieurs sites à fort trafic, la même procédure s'applique aux serveurs dédiés Eco et Performance.

Principe GitOps appliqué à WordPress

ÉlémentOù il vitVersionné dans Git ?
Cœur WordPress, plugins et thèmes publicscomposer.json + composer.lockOui (déclaration + versions exactes)
Thème et plugins développés par vousweb/app/themes/, web/app/plugins/Oui (code source)
Configuration (constantes, environnements)config/Oui
Secrets (mots de passe, clés, sels).env sur chaque serveurNon
Médias (uploads)web/app/uploads/ sur le serveurNon (à sauvegarder)
Contenu (articles, pages, réglages)Base de données MySQL/MariaDBNon (à sauvegarder)

Le cycle de vie devient :

  1. Vous modifiez le code ou mettez à jour une dépendance en local
  2. Vous committez et poussez sur la branche main
  3. Le pipeline CI installe les dépendances et déploie une nouvelle release sur le serveur
  4. En cas de problème, vous revenez à la release précédente (git revert ou bascule de lien symbolique)
Ce qui reste hors de Git

La base de données et les médias sont des données, pas du code. Ils ne sont pas gérés par Git : mettez en place des sauvegardes régulières (wp db export, sauvegarde du dossier shared/uploads, snapshots du VPS).

Prérequis

Sur votre poste de développement :

  • Git
  • PHP ≥ 8.1 (8.3 recommandé) et Composer 2
  • Un compte GitHub (ou GitLab) pour héberger le dépôt

Sur votre VPS HMS (Ubuntu 24.04 LTS / Debian 12) :

  • Accès SSH root ou utilisateur avec sudo
  • Un VPS sécurisé (utilisateur non-root, SSH, pare-feu) : voir Sécuriser son serveur
  • Un nom de domaine dont l'enregistrement DNS A pointe vers l'adresse IP du VPS
Pas de Composer en production

Dans ce guide, composer install est exécuté par le pipeline CI. Le VPS ne reçoit que des fichiers déjà prêts : il n'a besoin ni de Composer, ni de Git, ni d'accès aux dépôts de paquets.

Étape 1 : créer le projet Bedrock en local

  1. Créez le projet :

    composer create-project roots/bedrock mon-site
    cd mon-site
  2. Découvrez l'arborescence :

    mon-site/
    ├── composer.json # Cœur WordPress, plugins et thèmes déclarés
    ├── composer.lock # Versions exactes installées
    ├── .env.example # Modèle de configuration
    ├── wp-cli.yml # Indique à WP-CLI où se trouve WordPress (web/wp)
    ├── config/
    │ ├── application.php # Configuration principale (remplace wp-config.php)
    │ └── environments/
    │ ├── development.php
    │ └── staging.php
    ├── vendor/ # Dépendances Composer (non versionné)
    └── web/ # Racine du serveur web (document root)
    ├── app/ # Équivalent de wp-content
    │ ├── mu-plugins/
    │ ├── plugins/
    │ ├── themes/
    │ └── uploads/
    ├── wp/ # Cœur WordPress (non versionné)
    ├── wp-config.php
    └── index.php
    info

    Avec Bedrock, l'administration se trouve à l'adresse https://votre-domaine.com/wp/wp-admin et wp-content est remplacé par web/app.

  3. Créez votre fichier .env local à partir du modèle :

    cp .env.example .env
    .env
    DB_NAME='wordpress_dev'
    DB_USER='wordpress_user'
    DB_PASSWORD='mot_de_passe_local'
    DB_HOST='localhost'
    DB_PREFIX='wp_'

    WP_ENV='development'
    WP_HOME='http://mon-site.test'
    WP_SITEURL="${WP_HOME}/wp"

    AUTH_KEY='generateme'
    SECURE_AUTH_KEY='generateme'
    LOGGED_IN_KEY='generateme'
    NONCE_KEY='generateme'
    AUTH_SALT='generateme'
    SECURE_AUTH_SALT='generateme'
    LOGGED_IN_SALT='generateme'
    NONCE_SALT='generateme'
  4. Générez des clés et sels uniques (à faire pour chaque environnement) :

    for k in AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT; do
    echo "$k='$(openssl rand -base64 48 | tr -d '\n')'"
    done

    Copiez le résultat dans votre .env à la place des lignes generateme. Vous pouvez aussi utiliser le générateur de Roots.

Environnement local

Pour faire tourner le site en local, utilisez l'outil de votre choix (DDEV, Lando, Laravel Valet, Docker…) en faisant pointer la racine web sur le dossier web/.

Étape 2 : gérer plugins et thèmes avec Composer

Bedrock est préconfiguré avec WPackagist, un miroir Composer de tous les plugins et thèmes du répertoire officiel WordPress.org.

  1. Installez un plugin ou un thème :

    composer require wpackagist-plugin/wordpress-seo
    composer require wpackagist-plugin/wp-mail-smtp
    composer require wpackagist-theme/twentytwentyfive

    Le nom du paquet correspond au slug WordPress.org (https://wordpress.org/plugins/<slug>/).

  2. Mettez à jour les dépendances :

    # Tout mettre à jour selon les contraintes de composer.json
    composer update

    # Mettre à jour uniquement le cœur WordPress
    composer update roots/wordpress --with-all-dependencies

    # Voir les mises à jour disponibles
    composer outdated
  3. Supprimez un plugin :

    composer remove wpackagist-plugin/wp-mail-smtp
Toujours committer composer.lock

C'est le fichier composer.lock qui garantit que la production installe exactement les mêmes versions que celles testées en local. Il doit toujours être versionné.

Plugins premium ou privés

Les plugins payants ne sont pas sur WPackagist. Deux solutions :

  • Recommandé : utiliser le dépôt Composer fourni par l'éditeur (ACF Pro, Gravity Forms, WPML… en proposent un) avec une clé d'authentification stockée dans auth.json (non versionné) et dans les secrets de votre CI.

  • Alternative : versionner le plugin directement dans le dépôt en ajoutant une exception au .gitignore :

    .gitignore
    web/app/plugins/*
    !web/app/plugins/.gitkeep
    !web/app/plugins/mon-plugin-premium

Votre propre thème (dans web/app/themes/) est versionné par défaut.

Étape 3 : ajuster la configuration

La configuration commune se trouve dans config/application.php. Les surcharges par environnement sont dans config/environments/<WP_ENV>.php. Par défaut, Bedrock :

  • désactive l'éditeur de fichiers et l'installation / mise à jour de plugins depuis l'administration (DISALLOW_FILE_MODS) en production
  • les autorise en development
  • masque les erreurs PHP en production

Ce comportement est au cœur du GitOps : en production, toute modification de code passe par Git. Si un plugin affiche « mise à jour disponible », faites la mise à jour avec Composer en local, testez, puis committez.

Exemple d'ajout d'une constante pour tous les environnements :

config/application.php
Config::define('WP_POST_REVISIONS', 10);
Config::define('WP_MEMORY_LIMIT', '256M');

Étape 4 : initialiser le dépôt Git

Bedrock fournit un .gitignore adapté : vendor/, web/wp/, les plugins installés par Composer, les uploads et le fichier .env sont ignorés.

git init -b main
git add .
git commit -m "Initialisation du projet Bedrock"
git remote add origin git@github.com:votre-compte/mon-site.git
git push -u origin main

Vérifiez qu'aucun secret n'est versionné :

git ls-files | grep -E '(^|/)\.env$|auth\.json' || echo "OK : aucun secret versionné"

Étape 5 : préparer le VPS HMS

Se connecter au VPS

Connectez-vous avec l'adresse IP et les identifiants reçus lors de la livraison :

ssh root@adresse_ip_du_vps

Installer Nginx, PHP-FPM et MariaDB

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mariadb-server \
php-fpm php-mysql php-curl php-gd php-intl php-mbstring php-xml php-zip php-imagick
php -v

Ubuntu 24.04 installe PHP 8.3 (Debian 12 : PHP 8.2). Dans la suite du guide, adaptez php8.3-fpm à la version affichée par php -v. Pour sécuriser MariaDB, suivez le guide Installer et sécuriser MariaDB.

Si le pare-feu UFW est actif, ouvrez les ports web :

sudo ufw allow 'Nginx Full'

Créer l'utilisateur de déploiement et l'arborescence

Le pipeline se connectera avec un utilisateur dédié deploy. Le code appartient à deploy et n'est qu'en lecture pour PHP (www-data) ; seul le dossier des uploads est accessible en écriture par PHP.

sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG www-data deploy

sudo mkdir -p /var/www/mon-site/{releases,shared/uploads}
sudo chown -R deploy:www-data /var/www/mon-site
sudo chown -R www-data:www-data /var/www/mon-site/shared/uploads
sudo chmod 2775 /var/www/mon-site/shared/uploads

L'arborescence de déploiement sera la suivante :

/var/www/mon-site/
├── current -> releases/<sha> # Lien symbolique vers la release active
├── releases/
│ ├── 3f2a9c1.../ # Une release par commit déployé
│ └── 8be41d0.../
└── shared/
├── .env # Configuration de production (hors Git)
└── uploads/ # Médias persistants entre les releases

Créer la base de données

sudo mysql -u root -p
CREATE DATABASE wordpress_prod DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'wordpress_user'@'localhost' IDENTIFIED BY 'mot_de_passe_securise';
GRANT ALL PRIVILEGES ON wordpress_prod.* TO 'wordpress_user'@'localhost';
FLUSH PRIVILEGES;
EXIT;

Créer le fichier .env de production

sudo -u deploy nano /var/www/mon-site/shared/.env
/var/www/mon-site/shared/.env
DB_NAME='wordpress_prod'
DB_USER='wordpress_user'
DB_PASSWORD='mot_de_passe_securise'
DB_HOST='localhost'
DB_PREFIX='wp_'

WP_ENV='production'
WP_HOME='https://votre-domaine.com'
WP_SITEURL="${WP_HOME}/wp"

# Clés et sels générés avec la commande openssl de l'étape 1
AUTH_KEY='...'
SECURE_AUTH_KEY='...'
LOGGED_IN_KEY='...'
NONCE_KEY='...'
AUTH_SALT='...'
SECURE_AUTH_SALT='...'
LOGGED_IN_SALT='...'
NONCE_SALT='...'
sudo chown deploy:www-data /var/www/mon-site/shared/.env
sudo chmod 640 /var/www/mon-site/shared/.env

Configurer Nginx

La racine web pointe sur current/web : le code, vendor/ et le .env ne sont jamais exposés.

/etc/nginx/sites-available/mon-site
server {
listen 80;
server_name votre-domaine.com www.votre-domaine.com;

root /var/www/mon-site/current/web;
index index.php;

client_max_body_size 64M;

location / {
try_files $uri $uri/ /index.php?$args;
}

# Empêcher l'exécution de PHP dans les uploads
location ~* ^/app/uploads/.*\.php$ {
deny all;
}

location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
# Résoudre le lien symbolique "current" pour chaque requête
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;
}

location ~ /\. {
deny all;
}
}
sudo ln -s /etc/nginx/sites-available/mon-site /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
$realpath_root

L'utilisation de $realpath_root (au lieu de $document_root) garantit que PHP-FPM charge les fichiers de la nouvelle release dès la bascule du lien current, sans servir d'anciens chemins en cache.

Activez ensuite HTTPS, par exemple avec Certbot (voir aussi Installer un certificat SSL) :

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d votre-domaine.com -d www.votre-domaine.com

Autoriser le rechargement de PHP-FPM

Le pipeline recharge PHP-FPM après chaque déploiement pour vider le cache OPcache. Autorisez uniquement cette commande pour l'utilisateur deploy :

echo 'deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload php8.3-fpm' | sudo tee /etc/sudoers.d/deploy
sudo chmod 440 /etc/sudoers.d/deploy
sudo visudo -c

Créer la clé SSH de déploiement

Sur votre poste, générez une paire de clés dédiée au pipeline :

ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./deploy_key -N ""

Ajoutez la clé publique sur le VPS :

sudo mkdir -p /home/deploy/.ssh
sudo nano /home/deploy/.ssh/authorized_keys # collez le contenu de deploy_key.pub
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh && sudo chmod 600 /home/deploy/.ssh/authorized_keys

Récupérez l'empreinte SSH du VPS (pour éviter toute attaque de type man-in-the-middle) :

ssh-keyscan -H adresse_ip_du_vps

Étape 6 : automatiser le déploiement avec GitHub Actions

Déclarer les secrets

Dans votre dépôt GitHub, allez dans Settings → Secrets and variables → Actions et créez :

SecretValeur
SSH_HOSTAdresse IP de votre VPS HMS
SSH_USERdeploy
SSH_PRIVATE_KEYContenu du fichier deploy_key (clé privée)
SSH_KNOWN_HOSTSRésultat de la commande ssh-keyscan

Supprimez ensuite les fichiers deploy_key et deploy_key.pub de votre poste.

Créer le workflow

.github/workflows/deploy.yml
name: Deploy

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: production
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
environment: production
env:
BASE_DIR: /var/www/mon-site
TARGET: ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}
steps:
- uses: actions/checkout@v4

- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: composer:v2

- name: Installer les dépendances
run: composer install --no-dev --prefer-dist --no-interaction --optimize-autoloader

- name: Configurer SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
chmod 600 ~/.ssh/id_ed25519

- name: Envoyer la release
run: |
rsync -az --delete \
--exclude='.git' --exclude='.github' \
--exclude='.env' --exclude='web/app/uploads' \
./ "$TARGET:$BASE_DIR/releases/$GITHUB_SHA/"

- name: Activer la release
run: |
ssh "$TARGET" bash -s -- "$BASE_DIR" "$GITHUB_SHA" <<'EOF'
set -euo pipefail
BASE_DIR="$1"
RELEASE="$BASE_DIR/releases/$2"

# Lier la configuration et les médias partagés
ln -sfn "$BASE_DIR/shared/.env" "$RELEASE/.env"
rm -rf "$RELEASE/web/app/uploads"
ln -sfn "$BASE_DIR/shared/uploads" "$RELEASE/web/app/uploads"

# Bascule atomique du lien "current"
ln -sfn "$RELEASE" "$BASE_DIR/current.tmp"
mv -Tf "$BASE_DIR/current.tmp" "$BASE_DIR/current"

# Vider OPcache
sudo systemctl reload php8.3-fpm

# Conserver les 5 dernières releases
cd "$BASE_DIR/releases"
ls -1t | tail -n +6 | xargs -r rm -rf
EOF

Committez et poussez ce fichier : le premier déploiement démarre automatiquement. Suivez son exécution dans l'onglet Actions du dépôt.

Variante GitLab

Le même principe s'applique avec GitLab CI : une image composer:2 pour le composer install, puis rsync et ssh avec des variables CI/CD protégées à la place des secrets GitHub.

Premier lancement

Une fois le premier déploiement réussi, installez WordPress. Soit via le navigateur à l'adresse https://votre-domaine.com/wp/wp-admin/install.php, soit avec WP-CLI depuis le serveur :

cd /var/www/mon-site/current
sudo -u www-data wp core install \
--url="https://votre-domaine.com" \
--title="Titre de votre site" \
--admin_user="admin" \
--admin_password="MotDePasseFort123!" \
--admin_email="votre@email.com"

Consultez le guide Installer WordPress avec WP-CLI pour installer WP-CLI sur le serveur.

Sitemap XML et référencement

WordPress génère nativement un plan du site (sitemap) à l'adresse https://votre-domaine.com/wp-sitemap.xml. Avec Bedrock et la configuration Nginx ci-dessus (try_files ... /index.php?$args), il fonctionne sans réglage supplémentaire. Vérifiez-le :

curl -sI https://votre-domaine.com/wp-sitemap.xml | head -n 1

Si vous installez un plugin SEO via Composer (par exemple composer require wpackagist-plugin/wordpress-seo), celui-ci remplace le sitemap natif par le sien (/sitemap_index.xml pour Yoast SEO). Le sitemap reste généré dynamiquement depuis la base de données : il n'y a rien à versionner dans Git.

Pensez ensuite à :

  • Déclarer l'URL du sitemap dans Google Search Console et Bing Webmaster Tools
  • Vérifier que Réglages → Lecture → Visibilité pour les moteurs de recherche n'est pas cochée en production
Staging non indexé

Bedrock inclut le mu-plugin bedrock-disallow-indexing : sur un environnement dont WP_ENV vaut staging ou development, le site demande aux moteurs de recherche de ne pas l'indexer (DISALLOW_INDEXING). Seule la production apparaît donc dans les résultats de recherche.

Travailler au quotidien

Mettre à jour WordPress et les plugins

composer update
# Tester le site en local, puis :
git add composer.json composer.lock
git commit -m "chore: mise à jour WordPress et plugins"
git push

Le déploiement se fait automatiquement. Pour être prévenu des nouvelles versions, activez Dependabot (écosystème composer) ou Renovate sur le dépôt : ils ouvriront des pull requests de mise à jour que vous n'aurez plus qu'à valider.

Utiliser une branche de staging

Pour valider les changements avant la production, créez un second environnement (sous-domaine staging.votre-domaine.com, autre dossier, autre base, WP_ENV='staging') et dupliquez le workflow en le déclenchant sur une branche staging. Le fichier config/environments/staging.php s'applique alors automatiquement.

Revenir en arrière (rollback)

Méthode GitOps (recommandée) : annulez le commit fautif, le pipeline redéploie l'état précédent.

git revert <sha_du_commit>
git push

Méthode d'urgence : rebasculez manuellement le lien current sur la release précédente, directement sur le serveur.

cd /var/www/mon-site
ls -1t releases/ # repérer la release précédente
ln -sfn "$PWD/releases/<sha_precedent>" current.tmp && mv -Tf current.tmp current
sudo systemctl reload php8.3-fpm
attention

Un rollback de code ne restaure pas la base de données. Si une mise à jour a modifié le schéma de la base, restaurez aussi une sauvegarde SQL réalisée avant le déploiement.

Migrer un site WordPress existant vers Bedrock

  1. Listez les plugins et thèmes installés sur l'ancien site : wp plugin list et wp theme list

  2. Ajoutez-les au projet Bedrock avec composer require wpackagist-plugin/<slug> (et versionnez vos développements spécifiques)

  3. Copiez l'ancien dossier wp-content/uploads/ dans /var/www/mon-site/shared/uploads/

  4. Importez la base de données puis corrigez les chemins des médias, qui passent de wp-content à app :

    cd /var/www/mon-site/current
    sudo -u www-data wp db import /chemin/vers/sauvegarde.sql
    sudo -u www-data wp search-replace '/wp-content/uploads' '/app/uploads' --all-tables
  5. Si le préfixe des tables n'est pas wp_, adaptez DB_PREFIX dans le .env

astuce

Faites un essai avec --dry-run avant tout wp search-replace pour vérifier le nombre de remplacements.

Bonnes pratiques

  • Ne modifiez jamais de fichiers directement sur le serveur : tout passe par Git
  • Committez toujours composer.lock et figez les versions sensibles dans composer.json
  • Gardez les secrets hors du dépôt (.env, auth.json) et utilisez les secrets de la CI
  • Protégez la branche main (revue obligatoire des pull requests)
  • Sauvegardez régulièrement la base de données et le dossier shared/uploads, et complétez avec les snapshots/sauvegardes de votre VPS
  • Déployez d'abord sur un environnement de staging

En cas de problème

  • Page blanche ou erreur 500 : vérifiez les logs sudo tail -f /var/log/nginx/error.log et /var/log/php8.3-fpm.log, ainsi que la présence du lien .env dans la release active
  • Erreur de connexion à la base de données : vérifiez les identifiants du fichier shared/.env
  • L'ancienne version reste affichée après déploiement : vérifiez que PHP-FPM a bien été rechargé et que Nginx utilise $realpath_root
  • Impossible d'envoyer des médias : vérifiez que shared/uploads appartient à www-data et que le lien web/app/uploads pointe bien dessus
  • Le pipeline échoue à la connexion SSH : contrôlez les secrets SSH_PRIVATE_KEY et SSH_KNOWN_HOSTS, et testez ssh deploy@adresse_ip_du_vps depuis votre poste
  • Pas d'installation de plugins depuis l'administration : c'est normal en production (DISALLOW_FILE_MODS), utilisez Composer