Automatisation des déploiements de sites Drupal avec CI/CD
Objectifs
- Build reproductible via Composer et verrouillage des dépendances
- Tests et qualité automatisés (statique et fonctionnel)
- Packaging d’artefacts immuables
- Déploiements idempotents et sans interruption
- Imports de configuration et mises à jour de base orchestrés
- Stratégie de rollback documentée
Préparation du dépôt
- Structure basée sur Composer (ex.: drupal/recommended-project)
- Verrouillage des versions: composer.lock commité
- Répertoires ignorés: vendor/, web/sites/*/files/, node_modules/
- Synchronisation de configuration: web/sites/default/files/config ou config/sync
- Gestion des confs par environnement: Config Split / Config Ignore
- Scripts Composer utiles:
- "scripts": {"build": ["npm ci --prefix web/themes/custom/THEME", "npm run build --prefix web/themes/custom/THEME"]}
- Alias Drush dans drush/sites pour cibler les environnements
Pipeline standard
- Déclenchement
- Branches principales: main/master → staging/production
- Branches de feature → tests et aperçus éphémères
- Tags → releases
- Build
- composer install --no-dev --prefer-dist --no-interaction
- Compilation front (npm/yarn) si nécessaire
- Optimisations: drush locale:check && drush locale:update si pertinent
- Qualité et tests
- phpcs + Drupal Coder
- phpstan/psalm
- phpunit (Kernel/Unit)
- Behat/Cypress en option (smoke E2E)
- Packaging
- Artefact: code construit + vendor + build front
- Exclusion des fichiers non nécessaires aux serveurs CI
- Déploiement
- Transfert (rsync/SSH, S3, ou image de conteneur)
- Stratégie de release: répertoires horodatés + symlink current
- Post-déploiement
- Mode maintenance ON
- drush updb -y
- drush cim -y
- drush cr
- Mode maintenance OFF
- Observabilité
- Logs d’étapes, métriques de performance, alertes
Exemple GitHub Actions
name: CI-CD Drupal
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
workflow_dispatch:
jobs:
build-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: PHP setup
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
tools: composer
coverage: none
- name: Composer install
run: composer install --no-interaction --prefer-dist
- name: Front build (optionnel)
run: |
npm ci --prefix web/themes/custom/THEME
npm run build --prefix web/themes/custom/THEME
- name: PHPCS
run: vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom web/themes/custom
- name: PHPUnit
run: vendor/bin/phpunit --colors=never
- name: Artefact
run: |
tar -czf build.tar.gz --exclude=.git --exclude=node_modules --exclude=.github .
- name: Upload artefact
uses: actions/upload-artifact@v4
with:
name: drupal-build
path: build.tar.gz
deploy-prod:
runs-on: ubuntu-latest
needs: build-test
if: github.ref == 'refs/heads/main'
steps:
- name: Download artefact
uses: actions/download-artifact@v4
with:
name: drupal-build
path: .
- name: Extract
run: tar -xzf build.tar.gz
- name: Deploy via rsync
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
SSH_KEY: ${{ secrets.SSH_KEY }}
REMOTE_DIR: ${{ secrets.REMOTE_DIR }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
rsync -az --delete --exclude="web/sites/*/files" -e "ssh -o StrictHostKeyChecking=no" ./ $SSH_USER@$SSH_HOST:$REMOTE_DIR/releases/$(date +%Y%m%d%H%M%S)/
- name: Symlink + post-deploy (Drush)
env:
SSH_HOST: ${{ secrets.SSH_HOST }}
SSH_USER: ${{ secrets.SSH_USER }}
REMOTE_DIR: ${{ secrets.REMOTE_DIR }}
DRUSH_ALIAS: ${{ secrets.DRUSH_ALIAS }}
run: |
RELEASE=$(ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "ls -1 $REMOTE_DIR/releases | sort | tail -n 1")
ssh $SSH_USER@$SSH_HOST "ln -sfn $REMOTE_DIR/releases/$RELEASE $REMOTE_DIR/current"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS sset system.maintenance_mode 1 -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS updb -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS cim -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS cr"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS sset system.maintenance_mode 0 -y"
Exemple GitLab CI
stages:
- build
- test
- deploy
variables:
COMPOSER_ALLOW_SUPERUSER: "1"
GIT_STRATEGY: clone
cache:
paths:
- vendor/
build:
stage: build
image: php:8.2-cli
script:
- apt-get update && apt-get install -y git unzip
- curl -sS | php -- --install-dir=/usr/local/bin --filename=composer
- composer install --no-interaction --prefer-dist
- tar -czf build.tar.gz --exclude=.git --exclude=node_modules .
artifacts:
paths:
- build.tar.gz
expire_in: 1 week
test:
stage: test
image: php:8.2-cli
dependencies:
- build
script:
- tar -xzf build.tar.gz
- vendor/bin/phpcs --standard=Drupal,DrupalPractice web/modules/custom web/themes/custom
- vendor/bin/phpunit --colors=never
deploy_production:
stage: deploy
dependencies:
- build
when: manual
only:
- main
script:
- tar -xzf build.tar.gz
- apt-get update && apt-get install -y rsync openssh-client
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
- rsync -az --delete -e "ssh -o StrictHostKeyChecking=no" ./ $SSH_USER@$SSH_HOST:$REMOTE_DIR/releases/$(date +%Y%m%d%H%M%S)/
- |
RELEASE=$(ssh -o StrictHostKeyChecking=no $SSH_USER@$SSH_HOST "ls -1 $REMOTE_DIR/releases | sort | tail -n 1")
ssh $SSH_USER@$SSH_HOST "ln -sfn $REMOTE_DIR/releases/$RELEASE $REMOTE_DIR/current"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS sset system.maintenance_mode 1 -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS updb -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS cim -y"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS cr"
ssh $SSH_USER@$SSH_HOST "$REMOTE_DIR/current/vendor/bin/drush @$DRUSH_ALIAS sset system.maintenance_mode 0 -y"
environment:
name: production
url:
Secrets et configuration
- Gestion sécurisée via GitHub Secrets / GitLab Variables
- Variables par environnement: DRUPAL_ENV, DB_* (hors dépôt)
- Clés SSH en lecture seule pour déploiement
- Masquage des variables et rotation périodique
Stratégies de déploiement
- Releases horodatées + symlink current pour zéro interruption
- Blue-Green ou Canary via load balancer/Kubernetes
- Conteneurs: image Docker construite en CI, déployée via Helm/Kustomize
Rollback
- Conservation d’un historique de releases
- Retour arrière via symlink vers release N-1
- Restauration base de données et fichiers (snapshots/backup automatisés)
Tests recommandés
- Statique: phpcs (Drupal, DrupalPractice), phpstan
- Unitaires/Kernel: phpunit
- Fonctionnels: Nightwatch/Behat/Cypress (smoke en préprod)
- Performance: Lighthouse/Autres en post-déploiement
Bonnes pratiques
- Branches protégées et revues obligatoires
- Exécution de drush cim/updb/cr uniquement en post-déploiement
- Fichiers et images servis depuis un stockage persistant ou CDN
- Logs centralisés et alerting sur erreurs PHP/Drupal
- Instrumentation APM et vérifications de santé
Outils utiles
- Drush, Deployer PHP, Ansistrano
- Composer, Drupal Coder, PHPStan/Psalm
- Docker/Podman, Helm, Kustomize
- Plateformes managées: Platform.sh, Pantheon, Acquia Cloud (intégrations CI/CD natives)
Résumé exécutable
- Build immuable avec Composer + artefact
- Qualité et tests avant packaging
- Déploiement atomique (releases + symlink)
- Drush updb/cim/cr orchestrés
- Backups + rollback simplifié
- Se connecter ou s'inscrire pour publier un commentaire