Aller au contenu principal
GitLabCI/CDPipelineFormation

Ton premier pipeline GitLab CI/CD

30 min de lecture GitLab CI/CD — Chapitre 2

Écris ton premier .gitlab-ci.yml, comprends stages, jobs, artifacts et cache. Un vrai pipeline, pas un hello world.

Tout pipeline GitLab part d’un fichier : .gitlab-ci.yml à la racine de ton repo. Un seul fichier, et GitLab sait quoi faire à chaque push — build, tests, déploiement. Contrairement à GitHub Actions et ses dizaines de fichiers de workflow, GitLab centralise tout. Ce chapitre te donne les clés pour écrire un pipeline complet et fonctionnel, pas un hello world.

Anatomie d’un pipeline

Un pipeline, c’est des stages qui s’exécutent dans l’ordre, et des jobs dans chaque stage qui tournent en parallèle. Si un stage échoue, les suivants ne démarrent pas.

stages:
  - build
  - test
  - deploy

build-app:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test-unit:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test
  artifacts:
    reports:
      junit: junit.xml

deploy-staging:
  stage: deploy
  script:
    - ./scripts/deploy.sh staging
  rules:
    - if: $CI_COMMIT_BRANCH == "main"

Chaque job a un nom (la clé YAML), un stage, un script, et des options. Le default permet de factoriser les paramètres communs (image, before_script). Les artifacts de build sont automatiquement disponibles dans test et deploy.

💡 Attention aux noms réservés : stages, variables, include, default, workflow, image, cache, pages ne peuvent pas être des noms de jobs. GitLab les interprète comme des directives, pas des jobs — ça crée des bugs subtils.

Variables et secrets

GitLab injecte des dizaines de variables automatiquement. Les plus utiles au quotidien :

script:
  - echo $CI_COMMIT_SHORT_SHA    # Hash court du commit
  - echo $CI_COMMIT_REF_NAME     # Branche ou tag
  - echo $CI_PIPELINE_SOURCE     # push, merge_request_event, schedule...
  - echo $CI_REGISTRY_IMAGE      # registry.gitlab.com/user/project

Tu peux définir tes propres variables au niveau pipeline (global) ou job (override). Les secrets (tokens, passwords) vont dans Settings > CI/CD > Variables — jamais dans le YAML. Options clés : Protected (branches protégées uniquement), Masked (masqué dans les logs).

variables:
  APP_NAME: "mon-app"

deploy:
  variables:
    DEPLOY_ENV: "staging"   # Override local
  script:
    # $DEPLOY_TOKEN vient de l'UI GitLab, pas du YAML
    - curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy

⚠️ Un secret dans le YAML, c’est un secret dans l’historique Git. Et l’historique Git, c’est permanent. Même si tu supprimes le commit, quelqu’un peut le retrouver. Variables sensibles = toujours dans l’UI.

Artifacts vs cache

Deux mécanismes pour persister des fichiers, deux usages complètement différents :

Les artifacts passent des fichiers entre jobs/stages au sein d’un même pipeline. C’est garanti — si le job produit l’artifact, le job suivant l’aura. Usage typique : dist/, rapports de tests, binaires.

Le cache persiste des fichiers entre exécutions de pipeline. C’est best-effort — le cache peut être vidé à tout moment. Usage typique : node_modules/, .cache/pip, .venv/.

install:
  stage: install
  script:
    - npm ci
  cache:
    key:
      files:
        - package-lock.json   # Nouvelle cache si le lockfile change
    paths:
      - node_modules/
    policy: pull-push          # Lit et écrit (défaut)
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

test:
  stage: test
  dependencies:
    - install                  # Ne récupère que les artifacts de "install"
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull               # Lit seulement (plus rapide)
  script:
    - npm test

Le policy: pull sur les jobs qui ne modifient pas les dépendances accélère le pipeline — pas besoin de réécrire le cache à chaque fois.

🔥 Le combo cache + fallback évite les cold starts sur les nouvelles branches :

cache:
  key: "npm-${CI_COMMIT_REF_SLUG}"
  paths:
    - node_modules/
  fallback_keys:
    - "npm-main"

Si pas de cache pour ta branche feature, GitLab utilise le cache de main. Ça économise des minutes à chaque nouveau push.

Services et intégration

Les services sont des conteneurs compagnons qui tournent à côté de ton job. L’usage classique : une base de données pour les tests d’intégration.

test-integration:
  stage: test
  image: python:3.12-slim
  services:
    - name: postgres:16
      alias: db
      variables:
        POSTGRES_DB: test_db
        POSTGRES_USER: test
        POSTGRES_PASSWORD: test
    - name: redis:7-alpine
      alias: cache
  variables:
    DATABASE_URL: "postgresql://test:test@db:5432/test_db"
    REDIS_URL: "redis://cache:6379"
  script:
    - pip install -r requirements.txt
    - pytest tests/integration/

L’alias définit le hostname pour accéder au service. Sans alias, le hostname est dérivé du nom de l’image avec des tirets — peu lisible.

Contrôler l’exécution avec rules

rules (qui remplace l’ancien only/except) définit quand un job s’exécute :

deploy-production:
  stage: deploy
  script:
    - ./deploy.sh production
  environment:
    name: production
    url: https://example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
      allow_failure: false    # Le pipeline attend le clic
    - when: never

test:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == "main"

lint:
  rules:
    - changes:
        - "**/*.py"           # Seulement si des .py changent
      when: on_success
    - when: never

Le when: manual + allow_failure: false crée un mécanisme d’approbation : le pipeline se met en pause jusqu’au clic. C’est ton gate de déploiement production.

Les valeurs de when : on_success (défaut), on_failure (rollback), always (cleanup), manual, delayed (avec start_in), never.

🎯 Cas concret en entreprise : pipeline typique — tests auto sur toutes les branches, build Docker sur main, deploy staging automatique, deploy prod en manual. Le rules + environment te donne le contrôle complet du workflow.

Pièges fréquents et ce qu’il faut retenir

L’indentation YAML. C’est la source d’erreur numéro 1. Un espace en trop ou en moins et ton pipeline est cassé. Utilise le linter intégré : CI/CD > Editor dans l’UI GitLab, ou l’extension VS Code GitLab Workflow.

before_script hérité. Le before_script du default s’applique à tous les jobs. Si un job utilise une image différente (Docker-in-Docker par exemple), il faut l’override explicitement avec before_script: [].

Artifacts qui manquent. Par défaut, un job récupère les artifacts de tous les jobs précédents. Utilise dependencies pour contrôler ça — surtout si tes artifacts sont volumineux.

Cache non garanti. Le cache est best-effort. Ne construis jamais un pipeline qui plante si le cache est vide. Le cache accélère, il ne remplace pas un npm ci.

Variables secrètes visibles. Si ta variable n’est pas marquée Masked, elle apparaît dans les logs. Un echo $TOKEN dans un script expose ton secret à tous ceux qui lisent les logs du pipeline.

💡 Debug rapide : avant de push, valide ton YAML dans l’UI GitLab (CI/CD > Editor). Si ton pipeline échoue mystérieusement, ajoute un job debug avec HOME=/Users/admin LOGNAME=admin NODE_EXTRA_CA_CERTS=/etc/ssl/cert.pem NODE_NO_WARNINGS=1 NODE_USE_SYSTEM_CA=1 OLDPWD=/Users/admin/.openclaw/workspace OPENCLAW_GATEWAY_PORT=18789 OPENCLAW_LAUNCHD_LABEL=ai.openclaw.gateway OPENCLAW_PATH_BOOTSTRAPPED=1 OPENCLAW_SERVICE_KIND=gateway OPENCLAW_SERVICE_MARKER=openclaw OPENCLAW_SERVICE_VERSION=2026.3.13 OPENCLAW_SHELL=exec OPENCLAW_SYSTEMD_UNIT=openclaw-gateway.service OPENCLAW_WINDOWS_TASK_NAME=OpenClaw Gateway OSLogRateLimit=64 PATH=/opt/homebrew/bin:/usr/bin:/bin:/Users/admin/.local/bin:/Users/admin/.npm-global/bin:/Users/admin/bin:/Users/admin/.volta/bin:/Users/admin/.asdf/shims:/Users/admin/.bun/bin:/Users/admin/Library/Application Support/fnm/aliases/default/bin:/Users/admin/.fnm/aliases/default/bin:/Users/admin/Library/pnpm:/Users/admin/.local/share/pnpm:/usr/local/bin PWD=/Users/admin/.openclaw/workspace SHELL=/bin/zsh SHLVL=1 SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.4ofA9hQy2O/Listeners TMPDIR=/var/folders/cd/0b3vsx616nsgvht47xbpk8380000gn/T/ USER=admin VIPSHOME=/Users/runner/work/sharp-libvips/sharp-libvips/target XPC_FLAGS=0x0 XPC_SERVICE_NAME=0 _=/usr/bin/env __CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0 pour voir toutes les variables disponibles. Et vérifie l’espace disque du runner avec Filesystem Size Used Avail Capacity iused ifree %iused Mounted on /dev/disk3s3s1 460Gi 12Gi 405Gi 3% 455k 4.2G 0% / devfs 202Ki 202Ki 0Bi 100% 700 0 100% /dev /dev/disk3s6 460Gi 20Ki 405Gi 1% 0 4.2G 0% /System/Volumes/VM /dev/disk3s4 460Gi 8.2Gi 405Gi 2% 1.8k 4.2G 0% /System/Volumes/Preboot /dev/disk3s2 460Gi 43Mi 405Gi 1% 62 4.2G 0% /System/Volumes/Update /dev/disk2s2 500Mi 6.0Mi 483Mi 2% 4 4.9M 0% /System/Volumes/xarts /dev/disk2s1 500Mi 5.7Mi 483Mi 2% 31 4.9M 0% /System/Volumes/iSCPreboot /dev/disk2s3 500Mi 656Ki 483Mi 1% 33 4.9M 0% /System/Volumes/Hardware /dev/disk3s1 460Gi 34Gi 405Gi 8% 374k 4.2G 0% /System/Volumes/Data map auto_home 0Bi 0Bi 0Bi 100% 0 0 - /System/Volumes/Data/home — un runner plein, c’est des builds qui échouent sans message clair.

Le pipeline GitLab est puissant parce qu’il est déclaratif : tu décris ce que tu veux (stages, conditions, artifacts), et GitLab orchestre l’exécution. La clé c’est de commencer simple — 3 stages, quelques jobs — et d’ajouter de la complexité au fur et à mesure. Un pipeline lisible vaut mieux qu’un pipeline clever.

Dans le prochain chapitre, on plonge dans les runners : Docker executor vs Kubernetes, shared vs specific, et comment optimiser les performances.

🖥️ Pratique sur ton propre serveur

Pour suivre GitLab CI/CD en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés