14.1 — Pipelines CI/CD
🎯 Objectif : qu’un push sur
maindéclenche tout — typecheck, lint, tests, build, scan sécu, déploiement — sans intervention humaine. Et qu’une PR génère un environnement de preview pour relire visuellement.
À l'issue de cet axe, tu sauras :
- Distinguer Continuous Integration, Delivery et Deployment
- Construire un workflow GitHub Actions multi-jobs (lint/test/build/deploy)
- Mettre en place un environnement éphémère par PR (preview)
- Sécuriser les secrets via OIDC plutôt que des tokens longs vivent
- Optimiser le temps d'exécution avec cache, matrice et reusable workflows
Confirmé
CI ≠ CD ≠ CD
Section intitulée « CI ≠ CD ≠ CD »Les acronymes se mélangent. Définitions claires :
| Sigle | Phrase qui résume |
|---|---|
| CI — Continuous Integration | Chaque push est testé automatiquement |
| CD — Continuous Delivery | Chaque commit prêt à partir en prod, déclenchement manuel |
| CD — Continuous Deployment | Chaque commit qui passe la CI part automatiquement en prod |
Tu peux faire CI sans CD. Tu ne peux pas faire CD sans CI.
La majorité des équipes B2B font Continuous Delivery : pipeline auto, dernier clic manuel pour la prod. Les équipes mûres (Deezer, Etsy, GitLab) font Continuous Deployment : 50+ déploiements/jour.
Étapes d’un pipeline standard
Section intitulée « Étapes d’un pipeline standard »push ┐ │ ▼ ┌──────────┐ → ┌──────┐ → ┌─────┐ → ┌───────┐ → ┌─────┐ → ┌────────┐ │ checkout │ │ lint │ │test │ │ build │ │scan │ │ deploy │ └──────────┘ └──────┘ └─────┘ └───────┘ └─────┘ └────────┘ │ │ └─ artifact ┘| Étape | Quoi | Outils typiques |
|---|---|---|
| Checkout | Cloner le code | actions/checkout |
| Setup | Runtime + cache | actions/setup-node, setup-python, setup-go |
| Lint | Style, conventions | ESLint v9, Biome, Ruff, golangci-lint |
| Typecheck | Cohérence types | tsc --noEmit, mypy, phpstan |
| Test | Unitaires + intégration | Vitest, pytest, Pest, Playwright |
| Build | Bundle / image | Vite, Next, Docker, esbuild |
| Scan sécu | Vulnérabilités, secrets | npm audit, Snyk, Trivy, gitleaks |
| Deploy | Pousser l’artefact | Vercel CLI, Fly CLI, kubectl, AWS CLI, Cloudflare Wrangler |
Règle d’or : chaque étape doit être reproductible localement. Si seule la CI sait builder, tu as un problème.
GitHub Actions — l’essentiel
Section intitulée « GitHub Actions — l’essentiel »Hello world — .github/workflows/ci.yml
Section intitulée « Hello world — .github/workflows/ci.yml »name: CI
on: push: branches: [main] pull_request:
concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true
jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 24 cache: npm - run: npm ci - run: npm run lint - run: npm run typecheck - run: npm test -- --reporter=verboseTrois mécaniques importantes :
concurrency+cancel-in-progress: si tu pushes 2× de suite, le 1ᵉʳ run est annulé.cache: npmintégré àsetup-node: restore + save lenode_modulescache.timeout-minutes: tu paies à la minute, plafonne tes runs.
Multi-jobs avec dépendances
Section intitulée « Multi-jobs avec dépendances »jobs: lint: runs-on: ubuntu-latest steps: [...]
test: runs-on: ubuntu-latest steps: [...]
build: needs: [lint, test] # ne tourne que si lint ET test ont réussi runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 24, cache: npm } - run: npm ci - run: npm run build - uses: actions/upload-artifact@v4 with: name: dist path: dist/ retention-days: 7
deploy: needs: build if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production steps: - uses: actions/download-artifact@v4 with: { name: dist, path: dist/ } - run: ./deploy.shArchitecture typique :
- Parallèle ce qui peut l’être (lint, typecheck, test).
- Séquentiel sur la chaîne de dépendances (build après tests).
- Conditionnel sur
mainpour le deploy.
Matrice de versions
Section intitulée « Matrice de versions »Tester sur plusieurs Node, Postgres, OS :
jobs: test: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] node: [20, 22, 24] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: ${{ matrix.node }}, cache: npm } - run: npm ci && npm testfail-fast: false = on continue les autres combos même si un échoue (utile pour debug).
Services intégrés (Postgres, Redis, etc.)
Section intitulée « Services intégrés (Postgres, Redis, etc.) »jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:17-alpine env: POSTGRES_PASSWORD: testpw ports: ['5432:5432'] options: >- --health-cmd "pg_isready" --health-interval 5s --health-timeout 3s --health-retries 5 env: DATABASE_URL: postgres://postgres:testpw@localhost:5432/postgres steps: - uses: actions/checkout@v4 - run: npm ci && npm run db:migrate && npm testCache — le plus gros levier
Section intitulée « Cache — le plus gros levier »Sans cache, chaque run réinstalle tout = 1-3 min perdues. Avec cache :
| Type | Action | Gain |
|---|---|---|
node_modules | setup-node avec cache: npm | -60 à -90 % |
.next/cache (Next.js build) | actions/cache@v4 | -30 à -70 % du build |
| Docker layers | docker/build-push-action avec cache-from/cache-to | -50 à -80 % |
| pip / pnpm / bun | setup-python / setup-pnpm / setup-bun | idem |
- uses: actions/cache@v4 with: path: | .next/cache ~/.cache/pip key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-build-Secrets — OIDC > tokens longs vivent
Section intitulée « Secrets — OIDC > tokens longs vivent »L’ancienne école : Personal Access Token
Section intitulée « L’ancienne école : Personal Access Token »env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Problème : le token vit indéfiniment dans GitHub Secrets, et un commit malveillant dans une PR peut tenter de l’exfiltrer.
La bonne école : OIDC (OpenID Connect)
Section intitulée « La bonne école : OIDC (OpenID Connect) »GitHub Actions signe un JWT court vivant prouvant « ce job vient bien du repo org/repo sur la branche main ». Ton cloud (AWS, GCP, Azure) le valide et délivre des credentials temporaires (15 min).
permissions: id-token: write # nécessaire pour OIDC contents: read
jobs: deploy: runs-on: ubuntu-latest steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/gha-deployer aws-region: eu-west-3 - run: aws s3 sync ./dist s3://my-bucketAvantages :
- Aucun secret stocké côté GitHub.
- Credentials éphémères (impossible à exfiltrer durablement).
- Restriction par branche côté IAM (rôle utilisable seulement depuis
main).
Disponible sur AWS, GCP, Azure, HashiCorp Vault, Cloudflare. À privilégier en 2026.
Environnements éphémères par PR
Section intitulée « Environnements éphémères par PR »L’idée : chaque PR a sa propre URL de preview. Les reviewers cliquent et voient.
Avec Vercel / Netlify / Cloudflare Pages
Section intitulée « Avec Vercel / Netlify / Cloudflare Pages »Aucun workflow à écrire — c’est natif. Connecte le repo, à chaque PR tu reçois https://my-app-pr-42.vercel.app.
Avec Render / Fly.io / Railway
Section intitulée « Avec Render / Fly.io / Railway »Idem en 1 clic via leurs intégrations GitHub.
Avec Kubernetes / IaaS — ArgoCD ou helmfile
Section intitulée « Avec Kubernetes / IaaS — ArgoCD ou helmfile »Plus complexe : un job CI déploie un namespace dédié pr-42, post-déploiement il commente la PR avec l’URL :
- name: Deploy preview run: helmfile apply --namespace=pr-${{ github.event.number }}
- name: Comment URL on PR uses: thollander/actions-comment-pull-request@v3 with: message: '🚀 Preview: https://pr-${{ github.event.number }}.preview.example.com'À la fermeture de la PR, un workflow closed détruit le namespace.
Reusable workflows et composite actions
Section intitulée « Reusable workflows et composite actions »Quand 5 services partagent les mêmes étapes, factorise :
on: workflow_call: inputs: node-version: { type: string, default: '24' }
jobs: run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: ${{ inputs.node-version }}, cache: npm } - run: npm ci && npm test# Dans un autre workflowjobs: test: uses: ./.github/workflows/_reusable-test.yml with: { node-version: '24' }Build matrix utile en monorepo
Section intitulée « Build matrix utile en monorepo »Avec paths ou un outil comme Turborepo / Nx, tu n’exécutes que les jobs touchés :
on: push: paths: - 'apps/web/**' - 'packages/ui/**' - '.github/workflows/web.yml'Pour aller plus loin, Turborepo Remote Cache partage les artefacts entre devs et CI : si un build a déjà tourné quelque part, on télécharge le résultat plutôt que de le rejouer.
Sécurité du pipeline
Section intitulée « Sécurité du pipeline »| Risque | Parade |
|---|---|
| Action tierce malveillante | Pin sur SHA : actions/checkout@8f4b... plutôt que @v4 (immutable) |
| Workflow injecté via PR forkée | pull_request_target à éviter sauf cas précis |
| Secret leakage dans logs | mask:: automatique, mais ne jamais echo $SECRET |
| Élévation de privilèges | permissions: minimum (contents: read par défaut) |
| Compromission du compte GitHub d’un dev | 2FA obligatoire org-wide, branch protection + required reviews |
| Action publique abandonnée | Audit régulier des SHA pinned, Dependabot Actions |
permissions: contents: read # default minimal id-token: write # explicite si OIDCAnti-patterns courants
Section intitulée « Anti-patterns courants »- CI verte = code parfait : non, juste « pas pire que la barre actuelle ».
- Tests qui flake : tolérer un flake = enseigner aux devs à ignorer la CI. Quarantaine ou fix.
- Pipeline > 15 min : les devs n’attendent plus, ils mergent à l’aveugle.
- Tout dans un seul job : pas de parallélisme, pas de réutilisation.
if: always()partout : masque les vrais problèmes.- Aucun cache : 2× plus de minutes facturées.
Auto-évaluation
Section intitulée « Auto-évaluation »Pour aller plus loin
Section intitulée « Pour aller plus loin »- GitHub Actions docs — docs.github.com/actions
- Awesome Actions — github.com/sdras/awesome-actions
- Configuring OpenID Connect — docs.github.com/…/oidc
- act — github.com/nektos/act — exécuter Actions en local
Suite : 14.2 — Infrastructure as Code pour provisionner et versionner ton infra.