infra / docker
Intégration CI/CD avec Docker
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).
| Étape | Rôle | Bloque la suite si elle échoue ? |
|---|---|---|
| build | Construit l'image | Oui |
| test | Exécute la suite de tests | Oui |
| scan | Vérifie les vulnérabilités connues | Oui |
| push | Publie l'image sur le registre | Non (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
# .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# --- 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# 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=maxRésumé
- Un pipeline solide enchaîne build -> test -> scan de vulnérabilités -> push -> déploiement, chaque étape pouvant bloquer la suivante.
docker buildxavec cache distant (--cache-from/--cache-to type=registry) accélère fortement des builds CI sans état persistant.buildx --platformconstruit une image multi-architecture (amd64/arm64) en une seule commande.
Exercices pratiques
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.