Skip to Content
DevOpsCI/CD — GitHub Actions

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 HEAD

Triggers (on)

ÉvénementDescription
pushÀ chaque push sur les branches listées
pull_requestÀ chaque PR ouverte ou mise à jour
schedulePlanification cron (ex. déploiement quotidien)
workflow_dispatchDéclenchement manuel depuis l’onglet Actions
push.tagsUniquement sur les tags (ex. v*)

push et pull_request sont indépendants. Un push sur main dé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@v4 installe Node.js sur le runner. Le with transmet des paramètres à l’action via matrix.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éfixeExempleUsage
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 un echo en PHP ou Python.

Configurer un secret

  1. GitHub → Settings → Secrets and variablesActions
  2. Cliquer New repository secret
  3. 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-keys fournit 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-test tourne sur chaque branche. build-and-deploy ne tourne que sur main grâce au filtre if:. 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 prerelease dé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-progress

Pipeline 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 -- --production

Partager 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-days contrô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, fixer 1 ou 2 pour 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.xml

L’action summary de 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]

group dé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 avec cancel-in-progress: true mettent 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: true

Garder les PRs sans annulation (cancel-in-progress: false ou omission du champ) permet de voir le résultat de chaque état intermédiaire de la PR.

ProblèmeCauseSolution
Variable d’environnement inaccessible dans un stepenv. n’est pas accessible sans passage explicitePasser via env: au niveau du step ou du job
Cache non restauréLa clé de cache change à chaque runUtiliser restore-keys pour un fallback partiel
Déploiement double (push + PR)Push et PR partagent la même brancheFiltrer avec if: github.ref == 'refs/heads/main'
Timeout d’installationRunner lent ou grande dépendanceAjouter timeout-minutes: 30 au niveau du job
Variable de contexte manquanteGITHUB_TOKEN non disponible dans certains contextesVérifier if: github.event_name == 'push'

Ressources