Retour au cours

infra / docker

Intégration CI/CD avec Docker

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Construire un pipeline CI/CD qui enchaîne build, tests, scan de sécurité, push et déploiement
  • Comprendre pourquoi chaque étape doit pouvoir bloquer les suivantes en cas d'échec
  • Exporter le cache de build vers un registre distant pour l'exploiter sur des runners éphémères
  • Construire une image multi-architecture (amd64/arm64) en une seule commande avec buildx
  • Isoler les tests de tout accès réseau accidentel pour des résultats reproductibles

Dans quel contexte ?

Une équipe déploie manuellement ses images depuis des mois, avec le risque récurrent d'oublier une étape (les tests, ou le scan de sécurité) sous la pression d'un déploiement urgent. Une fois qu'une image vulnérable finit par atteindre la production sans avoir été scannée, l'équipe met en place un pipeline GitLab CI qui refuse strictement de publier toute image n'ayant pas passé les tests ET le scan trivy — rendant cet oubli humain structurellement impossible.

Automatiser ce qu'on a fait à la main jusqu'ici

Toutes les commandes vues dans les leçons précédentes (build, test, scan de sécurité, push) ont été jusqu'ici tapées manuellement. En pratique, une équipe veut que ces étapes se déclenchent automatiquement à chaque changement de code, de façon identique et fiable à chaque fois — sans dépendre de la mémoire ou de la rigueur d'une personne. C'est le rôle d'un pipeline d'intégration et de déploiement continus (CI/CD).

ÉtapeRôleBloque la suite si elle échoue ?
buildConstruit l'imageOui
testExécute la suite de testsOui
scanVérifie les vulnérabilités connuesOui
pushPublie l'image sur le registreNon (dernière étape avant deploy)

Prérequis

Cette leçon suppose que tu es à l'aise avec les multi-stage builds (leçon 9) et le scan de vulnérabilités (leçon sécurité) : un pipeline CI/CD ne fait qu'automatiser ces étapes déjà vues manuellement.

Un enchaînement d'étapes qui peuvent chacune tout arrêter

Un pipeline solide reproduit l'ordre logique déjà vu dans le cours : on construit l'image, on la teste, on scanne ses vulnérabilités, et seulement si tout est vert, on la publie puis on la déploie. L'intérêt de séparer ces étapes est qu'un échec à n'importe laquelle bloque immédiatement les suivantes — impossible de déployer en production une image qui n'a pas passé ses tests ou qui contient une faille critique.

Pourquoi le cache de build devient un vrai sujet en CI

En local, le cache de build de Docker reste sur la même machine d'un build à l'autre. En intégration continue, chaque exécution démarre souvent sur une machine "propre" (un runner éphémère), sans aucun historique de build précédent — ce qui annule tout l'intérêt du cache vu dans la leçon sur l'optimisation d'images. La solution consiste à exporter ce cache vers un emplacement partagé (un registre distant, par exemple), pour que le prochain build, même sur une autre machine, puisse le retrouver.

Construire pour plusieurs architectures

Une image construite sur une machine à base de processeurs Intel/AMD ne fonctionne pas nativement sur un processeur ARM (comme les puces Apple Silicon ou certains serveurs cloud). buildx permet de construire une image compatible avec plusieurs architectures en une seule commande, ce qui évite de maintenir des pipelines séparés par type de machine cible.

Isoler les tests du réseau

Lancer les tests sans accès réseau (--network none) est une bonne pratique : elle évite qu'un test dépende accidentellement d'un service externe indisponible.

Astuce

Un pipeline CI instable (qui échoue parfois sans lien avec le code modifié) est souvent le signe d'un test qui dépend d'un service externe non maîtrisé. Isoler les tests avec --network none force à identifier et corriger ces dépendances cachées, plutôt que de relancer le pipeline en espérant que ça passe.

Commandes & code

Intégration CI/CD avec Docker

yaml
# .gitlab-ci.yml : pipeline typique build -> test -> scan -> push -> déploiement
stages: [build, test, scan, push, deploy]

variables:
  IMAGE: registry.exemple.com/equipe/mon-api

build:
  stage: build
  script:
    - docker build --target test -t $IMAGE:test-$CI_COMMIT_SHORT_SHA .
    - docker build --target production -t $IMAGE:$CI_COMMIT_SHORT_SHA .

test:
  stage: test
  script:
    - docker run --rm $IMAGE:test-$CI_COMMIT_SHORT_SHA pytest

scan:
  stage: scan
  script:
    - trivy image --exit-code 1 --severity CRITICAL $IMAGE:$CI_COMMIT_SHORT_SHA

push:
  stage: push
  script:
    - echo "$REGISTRY_PASSWORD" | docker login registry.exemple.com -u "$REGISTRY_USER" --password-stdin
    - docker tag $IMAGE:$CI_COMMIT_SHORT_SHA $IMAGE:latest
    - docker push $IMAGE:$CI_COMMIT_SHORT_SHA
    - docker push $IMAGE:latest
  only:
    - main

deploy:
  stage: deploy
  script:
    - ssh deploy@prod "docker pull $IMAGE:$CI_COMMIT_SHORT_SHA && docker compose up -d"
  only:
    - main
bash
# --- BuildKit : accélère les builds CI (cache distant, montages, parallélisation) ---
export DOCKER_BUILDKIT=1
docker build -t mon-api:1.0 .

# Cache distant : partage le cache de build ENTRE différents runners CI (sans état local persistant)
docker buildx build \
  --cache-from type=registry,ref=$IMAGE:buildcache \
  --cache-to type=registry,ref=$IMAGE:buildcache,mode=max \
  -t $IMAGE:$CI_COMMIT_SHORT_SHA --push .

# --- Multi-architecture (buildx) : une seule commande pour amd64 ET arm64 ---
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t $IMAGE:1.0 --push .

# --- Éviter de reconstruire toute l'image pour un simple changement de code (voir aussi cache layers) ---
# Ordonner le Dockerfile (dépendances avant code) reste le levier n°1 même en CI

# --- Rendre les runs de test reproductibles et rapides en CI ---
docker run --rm --network none $IMAGE:test-$CI_COMMIT_SHORT_SHA pytest --maxfail=1 -x
# --network none isole les tests unitaires de tout accès réseau accidentel
yaml
# GitHub Actions : équivalent condensé
name: build-and-push
on: { push: { branches: [main] } }
jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with: { registry: ghcr.io, username: ${{ github.actor }}, password: ${{ secrets.GITHUB_TOKEN }} }
      - uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/mon-org/mon-api:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Résumé

  • Un pipeline solide enchaîne build -> test -> scan de vulnérabilités -> push -> déploiement, chaque étape pouvant bloquer la suivante.
  • docker buildx avec cache distant (--cache-from/--cache-to type=registry) accélère fortement des builds CI sans état persistant.
  • buildx --platform construit une image multi-architecture (amd64/arm64) en une seule commande.

Exercices pratiques

1 disponible
1

Mission : verrouiller un pipeline qui a laissé passer une image vulnérable

Objectif : Corriger un pipeline CI/CD pour qu'aucune étape critique ne puisse plus être contournée, et accélérer les builds sur des runners éphémères.

Contexte

Une image vulnérable a atteint la production car l'étape de scan de sécurité du pipeline GitLab CI était mal configurée et ne bloquait pas réellement le pipeline en cas de faille critique. L'équipe veut aussi accélérer ses builds, ralentis car chaque runner CI démarre "propre", sans aucun cache.

Résoudre l’exercice →