Cloud & Déploiement angularforall.com

- CI/CD avec GitHub Actions : pipeline complète

Github-Actions Ci-Cd Devops Cloud Automatisation Workflow-Yaml Tests-Automatises Build-Docker Deploiement-Continu Secrets Matrix-Builds Self-Hosted-Runners Github Pipelines
CI/CD avec GitHub Actions : pipeline complète

Créez une pipeline CI/CD complète avec GitHub Actions : lint, tests automatisés, build Docker et déploiement automatique sur push pour livrer plus vite.

Concepts fondamentaux — workflow, job, step, runner

GitHub Actions est un système CI/CD natif de GitHub. Un workflow est un fichier YAML dans .github/workflows/ qui décrit quand et comment automatiser des tâches.

ConceptDescriptionAnalogie
WorkflowFichier YAML complet — déclenché par un événementScript shell complet
EventCe qui déclenche le workflow (push, PR, schedule...)Trigger / condition
JobEnsemble de steps sur un même runnerTâche parallélisable
StepCommande shell ou action réutilisableInstruction individuelle
RunnerMachine virtuelle qui exécute le job (ubuntu-latest, windows-latest, macos-latest)Serveur d'exécution
ActionPlugin réutilisable (ex: actions/checkout)Bibliothèque de step
# Structure d'un workflow GitHub Actions
# Fichier : .github/workflows/ci.yml

name: CI Pipeline  # Nom affiché dans l'onglet Actions

on:               # Événements déclencheurs
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:             # Un ou plusieurs jobs (exécution parallèle par défaut)
  build:
    runs-on: ubuntu-latest   # Runner : VM Ubuntu gratuite GitHub
    steps:                   # Séquence d'étapes
      - name: Checkout le code
        uses: actions/checkout@v4  # Action officielle GitHub

      - name: Installer Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Installer les dépendances
        run: npm ci  # ci = install propre depuis package-lock.json

Premier workflow — lint, test, build

Un workflow CI complet pour une application Node.js/Angular enchaîne : lint (qualité du code), tests unitaires, build de production.

# .github/workflows/ci.yml
name: CI — Lint, Test, Build

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  quality:
    name: Lint + Tests + Build
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'  # Cache le dossier ~/.npm automatiquement

      - name: Installer les dépendances
        run: npm ci

      - name: Lint ESLint
        run: npm run lint
        # Si cette étape échoue, les suivantes ne s'exécutent pas

      - name: Tests unitaires avec couverture
        run: npm run test -- --coverage --watchAll=false
        env:
          CI: true  # Désactive le mode watch interactif de Jest/Karma

      - name: Upload couverture vers Codecov (optionnel)
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          fail_ci_if_error: false  # Ne bloque pas le CI si Codecov est down

      - name: Build production
        run: npm run build -- --configuration=production

      - name: Upload des artifacts de build
        uses: actions/upload-artifact@v4
        with:
          name: build-dist
          path: dist/
          retention-days: 7  # Conserve les artifacts 7 jours

Secrets, variables d'environnement et sécurité

Ne jamais mettre de secrets en clair dans les fichiers YAML. GitHub chiffre les secrets et les masque automatiquement dans les logs.

Configurer les secrets dans GitHub

  • Aller dans le repo → Settings → Secrets and variables → Actions
  • Cliquer sur "New repository secret"
  • Nommer le secret en MAJUSCULES avec underscores (ex: DATABASE_URL)
  • GitHub masque automatiquement la valeur dans tous les logs
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Environnement GitHub avec ses propres secrets

    steps:
      - name: Déployer sur le VPS
        env:
          # Secrets injectés comme variables d'environnement
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
          DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}
          API_KEY: ${{ secrets.EXTERNAL_API_KEY }}
        run: |
          # La valeur de SSH_PRIVATE_KEY est masquée dans les logs
          echo "$SSH_PRIVATE_KEY" > /tmp/deploy_key
          chmod 600 /tmp/deploy_key
          ssh -i /tmp/deploy_key -o StrictHostKeyChecking=no \
            "$DEPLOY_USER@$DEPLOY_HOST" "cd /app && ./deploy.sh"
Variables vs Secrets : les variables (Settings → Variables) sont visibles dans les logs — pour les configs non sensibles (NODE_ENV, APP_VERSION). Les secrets sont masqués — pour les mots de passe, tokens, clés SSH.

Matrice de builds — tests multi-versions

La matrice permet d'exécuter le même job en parallèle avec différentes combinaisons de paramètres (versions Node, OS, navigateurs).

jobs:
  test-matrix:
    name: Tests Node ${{ matrix.node }} / ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false  # Continue les autres combinaisons même si une échoue
      matrix:
        node: ['18', '20', '22']
        os: [ubuntu-latest, windows-latest]
        # Exclusions : certaines combinaisons problématiques
        exclude:
          - node: '18'
            os: windows-latest
        # Inclusions : configs supplémentaires
        include:
          - node: '20'
            os: ubuntu-latest
            coverage: true  # Variable custom accessible via matrix.coverage

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}

      - run: npm ci

      - name: Tests
        run: npm test

      - name: Couverture (seulement pour la config marquée)
        if: ${{ matrix.coverage }}
        run: npm run test:coverage

Cache des dépendances — accélérer les builds

Sans cache, npm ci télécharge toutes les dépendances à chaque run. Avec le cache, les runs suivants utilisent le cache si package-lock.json n'a pas changé.

# Méthode 1 : Cache automatique via actions/setup-node
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # Cache ~/.npm automatiquement
    # Pour pnpm : cache: 'pnpm'
    # Pour yarn : cache: 'yarn'

# Méthode 2 : Cache manuel avec actions/cache (plus de contrôle)
- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

# La clé change uniquement si package-lock.json change
# hashFiles() calcule un hash du fichier — cache invalidé si le lock change
# Cache Angular (cache spécifique .angular)
- name: Cache Angular build cache
  uses: actions/cache@v4
  with:
    path: .angular/cache
    key: ${{ runner.os }}-angular-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-angular-

# Le cache .angular/cache accélère le rebuild incrémental de 30-60%

Déploiement automatique sur VPS / DigitalOcean

Workflow complet CI + CD : les tests doivent passer avant le déploiement. Le job deploy dépend du job test via needs.

# .github/workflows/deploy.yml
name: CI + Deploy Production

on:
  push:
    branches: [main]  # Déploie seulement depuis main

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: '20', cache: 'npm' }
      - run: npm ci
      - run: npm test
      - run: npm run build -- --configuration=production
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  deploy:
    needs: test             # Attend que test réussisse
    runs-on: ubuntu-latest
    environment: production # Optionnel : environnement avec approbation manuelle

    steps:
      - name: Download build artifact
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Setup SSH key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts

      - name: Sync les fichiers buildés vers le serveur
        run: |
          rsync -avz --delete \
            -e "ssh -i ~/.ssh/deploy_key" \
            dist/ \
            "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/var/www/myapp/"

      - name: Recharger Nginx
        run: |
          ssh -i ~/.ssh/deploy_key \
            "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \
            "sudo nginx -t && sudo systemctl reload nginx"

Build et push d'image Docker

# .github/workflows/docker.yml
name: Build et Push Docker Image

on:
  push:
    branches: [main]
    tags: ['v*.*.*']  # Déclenche aussi sur les tags de version

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Docker meta (génère les tags automatiquement)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}  # GitHub Container Registry
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha,prefix=sha-

      - name: Login GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # Token automatique, pas besoin de créer

      - name: Build et push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha      # Cache GitHub Actions
          cache-to: type=gha,mode=max

Notifications et gestion des échecs

# Notification Slack sur échec uniquement
- name: Notification Slack si échec
  if: failure()  # Seulement si le job a échoué
  uses: slackapi/slack-github-action@v1.26.0
  with:
    payload: |
      {
        "text": "❌ CI échoué sur *${{ github.repository }}*\nBranche: ${{ github.ref_name }}\nCommit: ${{ github.sha }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|Voir le run>"
      }
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
# Conditions d'exécution — contrôle fin des steps
steps:
  - name: Exécuté toujours (même si une étape précédente échoue)
    if: always()
    run: echo "Cleanup..."

  - name: Exécuté seulement si le job réussit
    if: success()
    run: echo "Deploy..."

  - name: Exécuté seulement si annulé
    if: cancelled()
    run: echo "Cancelled..."

  - name: Exécuté si la PR vient d'un fork
    if: github.event.pull_request.head.repo.fork == true
    run: echo "Restricted CI for forks..."

Bonnes pratiques et optimisations

  • Fixer les versions des actions : utiliser @v4 plutôt que @latest pour éviter les régressions silencieuses
  • Utiliser npm ci plutôt que npm install — déterministe, respecte exactement le lock file
  • Paralléliser les jobs indépendants — lint, test, build peuvent tourner en parallèle si les artifacts ne sont pas partagés
  • Limiter les permissions avec permissions: au niveau du job (principe du moindre privilège)
  • Cache les dépendances sur package-lock.json — gain de 1 à 3 minutes par run
  • Utiliser concurrency pour annuler les runs précédents quand un nouveau push arrive sur la même branche
# Annuler automatiquement les runs précédents sur la même branche
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # Annule le run précédent sur la même branche

# Permissions minimales (sécurité)
permissions:
  contents: read       # Lecture du code seulement
  packages: write      # Écriture sur ghcr.io seulement si nécessaire
  pull-requests: write # Pour commenter les PRs

Partager