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é.
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
| Usage | vCPU | RAM | Stockage |
|---|---|---|---|
| Site vitrine / blog | 1-2 | 2 Go | 20 Go NVMe |
| Site à trafic moyen, plusieurs sites | 2-4 | 4 Go | 40 Go NVMe |
| WooCommerce / fort trafic | 4+ | 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.
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ément | Où il vit | Versionné dans Git ? |
|---|---|---|
| Cœur WordPress, plugins et thèmes publics | composer.json + composer.lock | Oui (déclaration + versions exactes) |
| Thème et plugins développés par vous | web/app/themes/, web/app/plugins/ | Oui (code source) |
| Configuration (constantes, environnements) | config/ | Oui |
| Secrets (mots de passe, clés, sels) | .env sur chaque serveur | Non |
| Médias (uploads) | web/app/uploads/ sur le serveur | Non (à sauvegarder) |
| Contenu (articles, pages, réglages) | Base de données MySQL/MariaDB | Non (à sauvegarder) |
Le cycle de vie devient :
- Vous modifiez le code ou mettez à jour une dépendance en local
- Vous committez et poussez sur la branche
main - Le pipeline CI installe les dépendances et déploie une nouvelle release sur le serveur
- En cas de problème, vous revenez à la release précédente (
git revertou bascule de lien symbolique)
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
Apointe vers l'adresse IP du VPS
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
-
Créez le projet :
composer create-project roots/bedrock mon-sitecd mon-site -
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.phpinfoAvec Bedrock, l'administration se trouve à l'adresse
https://votre-domaine.com/wp/wp-adminetwp-contentest remplacé parweb/app. -
Créez votre fichier
.envlocal à partir du modèle :cp .env.example .env.envDB_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' -
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; doecho "$k='$(openssl rand -base64 48 | tr -d '\n')'"doneCopiez le résultat dans votre
.envà la place des lignesgenerateme. Vous pouvez aussi utiliser le générateur de Roots.
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.
-
Installez un plugin ou un thème :
composer require wpackagist-plugin/wordpress-seocomposer require wpackagist-plugin/wp-mail-smtpcomposer require wpackagist-theme/twentytwentyfiveLe nom du paquet correspond au slug WordPress.org (
https://wordpress.org/plugins/<slug>/). -
Mettez à jour les dépendances :
# Tout mettre à jour selon les contraintes de composer.jsoncomposer update# Mettre à jour uniquement le cœur WordPresscomposer update roots/wordpress --with-all-dependencies# Voir les mises à jour disponiblescomposer outdated -
Supprimez un plugin :
composer remove wpackagist-plugin/wp-mail-smtp
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:.gitignoreweb/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::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
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.
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
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 :
| Secret | Valeur |
|---|---|
SSH_HOST | Adresse IP de votre VPS HMS |
SSH_USER | deploy |
SSH_PRIVATE_KEY | Contenu du fichier deploy_key (clé privée) |
SSH_KNOWN_HOSTS | Résultat de la commande ssh-keyscan |
Supprimez ensuite les fichiers deploy_key et deploy_key.pub de votre poste.
Créer le workflow
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.
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
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
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
-
Listez les plugins et thèmes installés sur l'ancien site :
wp plugin listetwp theme list -
Ajoutez-les au projet Bedrock avec
composer require wpackagist-plugin/<slug>(et versionnez vos développements spécifiques) -
Copiez l'ancien dossier
wp-content/uploads/dans/var/www/mon-site/shared/uploads/ -
Importez la base de données puis corrigez les chemins des médias, qui passent de
wp-contentàapp:cd /var/www/mon-site/currentsudo -u www-data wp db import /chemin/vers/sauvegarde.sqlsudo -u www-data wp search-replace '/wp-content/uploads' '/app/uploads' --all-tables -
Si le préfixe des tables n'est pas
wp_, adaptezDB_PREFIXdans le.env
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.locket figez les versions sensibles danscomposer.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.loget/var/log/php8.3-fpm.log, ainsi que la présence du lien.envdans 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/uploadsappartient àwww-dataet que le lienweb/app/uploadspointe bien dessus - Le pipeline échoue à la connexion SSH : contrôlez les secrets
SSH_PRIVATE_KEYetSSH_KNOWN_HOSTS, et testezssh deploy@adresse_ip_du_vpsdepuis votre poste - Pas d'installation de plugins depuis l'administration : c'est normal en production (
DISALLOW_FILE_MODS), utilisez Composer