CI/CD — GitHub Actions
Automatiser les builds, tests et déploiements via GitHub Actions.
GitHub Actions permet d’automatiser les workflows (CI/CD) depuis le dépôt lui-même. Un fichier YAML dans
.github/workflows/définit le pipeline.
Structure d’un workflow
Chaque workflow est défini dans un fichier .github/workflows/*.yml.
name: Pipeline CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lister les fichiers modifiés
run: git diff --name-only HEAD~1 HEADTriggers (on)
| Événement | Description |
|---|---|
push | À chaque push sur les branches listées |
pull_request | À chaque PR ouverte ou mise à jour |
schedule | Planification cron (ex. déploiement quotidien) |
workflow_dispatch | Déclenchement manuel depuis l’onglet Actions |
push.tags | Uniquement sur les tags (ex. v*) |
pushetpull_requestsont indépendants. Un push surmaindéclenche les deux si les deux sont configurés.
Jobs et étapes (jobs, steps)
Un job s’exécute sur un runner. Un step est une commande ou un appel d’action.
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm test
actions/setup-node@v4installe Node.js sur le runner. Lewithtransmet des paramètres à l’action viamatrix.node-version.
Secrets et variables
Utiliser un secret
jobs:
deploy:
steps:
- uses: actions/checkout@v4
- run: echo "${{ secrets.DB_PASSWORD }}"Variables de contexte GitHub Actions
| Préfixe | Exemple | Usage |
|---|---|---|
secrets.X | ${{ secrets.DB_PASSWORD }} | Secrets du dépôt |
github.ref | ${{ github.ref }} | Référence Git (branche ou tag) |
github.sha | ${{ github.sha }} | SHA du commit |
github.event_name | ${{ github.event_name }} | Événement déclencheur |
env.X | ${{ env.DATABASE_URL }} | Variable d’environnement du job |
runner.os | ${{ runner.os }} | OS du runner (Linux, macOS, Windows) |
Ne jamais afficher un secret en clair. Les logs GitHub masquent automatiquement les valeurs issues de
secrets.*quand elles sont passées en argument de commande, mais pas si elles sont interpolées dans unechoen PHP ou Python.
Configurer un secret
- GitHub → Settings → Secrets and variables → Actions
- Cliquer New repository secret
- Nommer le secret et coller la valeur
Cache des dépendances
Accélérer les builds en mettant en cache node_modules ou vendor/.
Node.js
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
restore-keysfournit un fallback partiel. GitHub restaure le cache complet quand la clé correspond exactement, sinon il restaure le cache le plus proche.
PHP / Laravel
- uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-Nettoyage du cache
# Lister les caches d'un dépôt
gh api repos/{owner}/{repo}/actions/cache --jq '.actions_caches[] | "\(.size_in_bytes) \(.\\.key)"'
# Supprimer un cache
gh api -X DELETE repos/{owner}/{repo}/actions/cache/keys/{cache_id}Pipeline complet : Node.js
Exemple concret de CI/CD pour une application Node.js.
name: Node.js CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Linting
run: npm run lint
- name: Tests
run: npm test
build-and-deploy:
needs: lint-and-test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- name: Déploiement
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
DEPLOY_HOST: ${{ secrets.HOST }}
DEPLOY_USER: ${{ secrets.USER }}
DEPLOY_PATH: /var/www/app
run: |
mkdir -p ~/.ssh
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no \
$DEPLOY_USER@$DEPLOY_HOST \
"cd $DEPLOY_PATH && git pull origin main && npm ci --production && pm2 restart all"Le job
lint-and-testtourne sur chaque branche.build-and-deployne tourne que surmaingrâce au filtreif:. Cela évite les déploiements accidentels depuis des branches de feature.
Pipeline complet : Laravel
Exemple de pipeline pour une application Laravel.
name: Laravel CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: ${{ secrets.DB_PASSWORD }}
MYSQL_DATABASE: test
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'
extensions: mbstring, pdo_mysql, zip, bcmath
coverage: none
- uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- run: composer install --prefer-dist --no-interaction
- run: cp .env.ci .env
if: hashFiles('.env.ci') != ''
- run: php artisan key:generate
- run: php artisan migrate --force
- run: php artisan test --parallel
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Déploiement par SSH
env:
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
echo "$SSH_PRIVATE_KEY" > deploy_key && chmod 600 deploy_key
ssh -i deploy_key -o StrictHostKeyChecking=no ${{ secrets.USER }}@${{ secrets.HOST }} "
cd /var/www/app &&
git pull origin main &&
composer install --no-dev --optimize-autoloader &&
php artisan migrate --force &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache &&
php artisan event:cache &&
pm2 restart all
"Tags et versioning automatique
Bump de version sur tag
name: Publish
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Extraire le numéro de version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build
- name: Créer un Release GitHub
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ steps.version.outputs.version }}
draft: false
prerelease: ${{ contains(steps.version.outputs.version, '-') }}La condition
prereleasedétecte automatiquement les versions pré-release (ex.1.0.0-beta.1) à partir du tiret dans le numéro de version.
Workflow sélectif (fichiers modifiés)
Lorsque seul un petit nombre de fichiers est modifié, un test complet peut rester long (migration de base de données). Un test rapide comme PHPStan permet de garder la boucle de feedback fraîche.
name: Fast lint
on:
push:
paths:
- 'src/**/*.php'
- 'composer.json'
pull_request:
paths:
- 'src/**/*.php'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
tools: phpstan:latest
- uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
- run: composer install --no-scripts --no-interaction
- run: php vendor/bin/phpstan analyse --no-progressPipeline condensé (projet simple)
Pour un projet simple, un seul fichier de workflow suffit :
name: CI
on: [push, pull_request]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm run lint || true
- run: npm test
- run: npm run build
cd:
needs: ci
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci
- run: npm run build -- --productionPartager des artefacts entre jobs
Les jobs d’un workflow s’exécutent sur des runners distincts. Pour transmettre un fichier (build, rapport de tests, binaire) d’un job à un autre, utiliser les actions upload-artifact et download-artifact.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run build
- name: Sauvegarder le build
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 1
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Récupérer le build
uses: actions/download-artifact@v4
with:
name: build-output
path: dist/
- name: Déploiement
run: echo "Déploiement depuis dist/"
retention-dayscontrôle combien de temps l’artefact est conservé sur les serveurs de GitHub (1-90 jours). Par défaut : 90 jours. Pour un artefact temporaire, fixer1ou2pour libérer l’espace de stockage.
Les artefacts sont stockés sous forme d’archives ZIP. La taille maximale par artefact est de 5 Go. Au-delà, découper en plusieurs artefacts par répertoire ou utiliser un stockage externe (S3, GCS).
Quand un artefact est téléchargé, l’ordre des fichiers dans l’archive est perdu. Si l’ordre compte (ex. fichiers de migration), compresser au format tar (
tar -czf) plutôt que ZIP.
Reporter des résultats de tests (JUnit)
GitHub Actions peut afficher les résultats de tests directement dans l’onglet Actions via un fichier XML JUnit.
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test -- --ci --reporters=default --reporters=jest-junit
- name: Reporter les résultats
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: junit.xmlL’action
summaryde GitHub (disponible dans les étapes) ajoute un tableau récapitulatif directement sur la page du workflow. Combiner avec un template Markdown pour un résumé lisible.
Concurrence des workflows
Par défaut, chaque push ou PR déclenche un nouveau run. Sur une branche active avec des pushes multiples, plusieurs runs s’empilent et consomment les minutes de CI allouées.
Annuler les runs en attente
name: Mon pipeline
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
groupdéfinit le groupe de runs qui se concentrent. Ici, tous les runs sur la même branche (refs/heads/main) appartiennent au même groupe. Quand un nouveau run se déclenche, les runs précédents aveccancel-in-progress: truemettent fin aux exécutions antérieures du même groupe qui tournent encore.
Grouper par événement
Pour n’annuler que les pushes (pas les PRs, qui doivent toutes être évaluées) :
concurrency:
group: push-${{ github.ref }}
cancel-in-progress: trueGarder les PRs sans annulation (
cancel-in-progress: falseou omission du champ) permet de voir le résultat de chaque état intermédiaire de la PR.
| Problème | Cause | Solution |
|---|---|---|
Variable d’environnement inaccessible dans un step | env. n’est pas accessible sans passage explicite | Passer via env: au niveau du step ou du job |
| Cache non restauré | La clé de cache change à chaque run | Utiliser restore-keys pour un fallback partiel |
| Déploiement double (push + PR) | Push et PR partagent la même branche | Filtrer avec if: github.ref == 'refs/heads/main' |
| Timeout d’installation | Runner lent ou grande dépendance | Ajouter timeout-minutes: 30 au niveau du job |
| Variable de contexte manquante | GITHUB_TOKEN non disponible dans certains contextes | Vérifier if: github.event_name == 'push' |