Tous les articles
Mer. 4 février 2026 · 3 min de lecture

🤖 GitHub Actions pour une application Laravel : la pipeline que j'utilise

GitHub Actions

📚 Introduction

Une CI doit faire trois choses : être rapide, dire la vérité (pas de fausses alertes), et être lisible quand quelque chose se casse. Pour une application Laravel, l’expérience montre qu’on peut tomber facilement dans le piège inverse : pipelines de 10 à 15 minutes, étapes redondantes, et impossible de comprendre pourquoi un job a échoué quand on revient dessus le lendemain.

Voici la structure que j’utilise par défaut sur les projets Laravel, et qui m’a permis de descendre la plupart des pipelines sous les trois minutes.

🧱 Structure générale

Trois jobs en parallèle, un job final qui agrège :

name: CI
 
on:
  pull_request:
  push:
    branches: [main]
 
jobs:
  lint:
    uses: ./.github/workflows/lint.yml
  static-analysis:
    uses: ./.github/workflows/static.yml
  test:
    uses: ./.github/workflows/test.yml
  ready:
    needs: [lint, static-analysis, test]
    runs-on: ubuntu-latest
    steps:
      - run: echo "✅ Tous les checks ont passé"

L’avantage de cette découpe : chaque job tourne sur sa propre VM, en parallèle, avec ses propres dépendances. Quand l’un échoue, on sait exactement où regarder. Et la règle de protection de branche n’a qu’à exiger le job ready pour bloquer le merge.

⚡ Les optimisations qui changent tout

Quelques choix concrets qui font gagner des minutes :

1. Cacher Composer correctement.

- uses: actions/cache@v4
  with:
    path: vendor
    key: composer-${{ runner.os }}-${{ hashFiles('composer.lock') }}

Avec le bon key, l’installation Composer est quasi-instantanée tant que composer.lock n’a pas bougé.

2. Utiliser setup-php avec coverage à none quand on n’en a pas besoin.

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

Activer Xdebug ou PCOV sur les jobs qui ne génèrent pas de coverage ajoute facilement 30 à 60 secondes par étape pour rien.

3. Découper les tests par groupe.

Avec PHPUnit ou Pest, on peut découper les suites par tag (unit, feature, integration) et faire tourner chaque groupe sur un job différent. Si l’un casse, on relance uniquement celui-ci.

4. Mutualiser le build des assets.

Si plusieurs jobs ont besoin des assets compilés (tests E2E, build de l’image Docker), construire une seule fois et publier en artefact :

- run: npm ci && npm run build
- uses: actions/upload-artifact@v4
  with:
    name: assets
    path: public/build

Les autres jobs téléchargent l’artefact au lieu de relancer le build.

🧪 Un job de test typique

test:
  runs-on: ubuntu-latest
  services:
    mysql:
      image: mysql:8
      env:
        MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
        MYSQL_DATABASE: testing
      ports: ['3306:3306']
      options: >-
        --health-cmd="mysqladmin ping"
        --health-interval=10s
        --health-timeout=5s
        --health-retries=3
  steps:
    - uses: actions/checkout@v4
    - uses: shivammathur/setup-php@v2
      with:
        php-version: '8.3'
        coverage: pcov
    - uses: actions/cache@v4
      with:
        path: vendor
        key: composer-${{ hashFiles('composer.lock') }}
    - run: composer install --no-interaction --prefer-dist
    - run: cp .env.testing .env
    - run: php artisan key:generate
    - run: php artisan migrate --no-interaction
    - run: ./vendor/bin/pest --coverage --min=85

Quelques principes :

  • services: plutôt que docker-compose. Plus rapide à monter, mieux intégré au runner.
  • Migrations à chaque run : on évite les fixtures gelées qui finissent par diverger de la réalité.
  • Coverage avec seuil minimal : on fait sauter la pipeline si le pourcentage tombe sous le seuil, plutôt que d’afficher un chiffre que personne ne regarde.

🚀 Déploiement conditionnel

Sur la branche principale uniquement, on déclenche un déploiement vers staging :

deploy-staging:
  needs: ready
  if: github.ref == 'refs/heads/main'
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - run: |
        curl -fsSL -X POST \
          -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
          ${{ secrets.COOLIFY_WEBHOOK }}

Coolify ou Vercel acceptent un simple POST pour déclencher un redéploiement à partir de la dernière version. Pas besoin de SSH, pas besoin d’agent personnalisé.

⚠️ Quelques pièges classiques

  • Ne pas mettre la coverage en obligatoire dans le job principal. Un seuil de coverage peut bloquer un fix urgent. Préférer un job séparé coverage-report non bloquant.
  • Surveiller le temps des services: à monter. MySQL 8 met une vingtaine de secondes. Sur une CI où chaque seconde compte, parfois SQLite suffit pour les tests d’unité.
  • Toujours figer les versions des actions tierces sur un tag majeur, voire un SHA, pour éviter les supply chain attacks.

🎉 Conclusion

Une pipeline Laravel rapide ne demande pas d’outils exotiques : une bonne découpe en jobs parallèles, un cache Composer bien configuré, des services adaptés et un déclenchement de déploiement minimaliste suffisent à descendre largement sous les 5 minutes.

À partir de là, on peut envisager des choses plus avancées : matrice multi-versions PHP, tests Playwright en parallèle, déploiement progressif avec rollback automatique. Mais le socle reste celui présenté ici.

🔗 Liens utiles