🤖 GitHub Actions pour une application Laravel : la pipeline que j'utilise
📚 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:2Activer 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/buildLes 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=85Quelques principes :
services:plutôt quedocker-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-reportnon 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.