CI/CD
& Docker
Intégration et déploiement continus — pipelines, Git workflows, GitHub Actions et conteneurs Docker.
CI / CD / CD — les trois notions
| Sigle | Nom | Définition |
|---|---|---|
| CI | Continuous Integration Intégration Continue |
Chaque commit déclenche automatiquement un build et des tests. Le code de tous les développeurs est intégré fréquemment (plusieurs fois par jour) dans une branche commune. |
| CD | Continuous Delivery Livraison Continue |
Le code validé par la CI est automatiquement préparé et prêt à être déployé en production. Le déploiement final reste déclenché manuellement (un humain appuie sur le bouton). |
| CD | Continuous Deployment Déploiement Continu |
Tout commit validé est automatiquement déployé en production sans intervention humaine. Nécessite une confiance totale dans les tests automatisés. |
# Scénario classique sans CI/CD :
Semaine 1-3 : Alice développe feature/panier
Bob développe feature/paiement
Chaque dev travaille dans son coin
Semaine 4 : "Merge day" — catastrophe !
→ Conflits partout (les deux touchent auth/)
→ Les tests d'Alice cassent le code de Bob
→ Le build échoue depuis 3 jours
→ Impossible de savoir qui a cassé quoi
→ "Integration Hell" 🔥
# Avec CI :
Chaque jour, chaque commit → tests automatiques
→ Les conflits sont détectés le jour même
→ On sait immédiatement qui a cassé quoi
→ Le code est TOUJOURS dans un état livrable
# Règle d'or CI :
"Ne jamais laisser le build cassé."
Si le build est rouge → tout s'arrête jusqu'à
la correction. C'est la priorité #1.
Pipeline CI/CD — les étapes
PR ouverte
lint
type-check
integration
couverture
SonarQube
dépendances
artefact
registry
smoke tests
E2E tests
auto (CDepl)
monitoring
| Étape | But | Échoue si… |
|---|---|---|
| Lint | Vérifier le style et la syntaxe du code | Règles de formatage violées, erreurs de syntaxe |
| Tests unitaires | Vérifier chaque fonction isolément | Un test échoue, couverture sous le seuil |
| Tests intégration | Vérifier les composants ensemble (BDD, API) | Interaction entre modules brisée |
| Tests E2E | Simuler un utilisateur réel (Playwright, Cypress) | Flux utilisateur critique cassé |
| SAST | Analyse statique de sécurité (Snyk, Trivy) | Vulnérabilité critique dans les dépendances |
| Build artefact | Créer l'image Docker ou le package déployable | Erreur de compilation, Dockerfile invalide |
Fail fast : les étapes les plus rapides et les plus susceptibles d'échouer passent en premier. Inutile de lancer un deploy 10 minutes si le lint échoue en 30 secondes. Ordre typique : lint → type-check → tests unitaires → build → tests intégration → sécurité → deploy.
Feedback loop : l'objectif est d'informer le développeur le plus vite possible. Un pipeline CI devrait donner un retour en moins de 10 minutes. Au-delà, les développeurs arrêtent de regarder les résultats.
Pourquoi CI/CD ?
| Sans CI/CD | Avec CI/CD | |
|---|---|---|
| Fréquence déploiement | Mensuel / trimestriel | Pluriquotidien |
| Délai détection bug | Semaines | Minutes |
| Temps de rollback | Heures | Minutes (re-déployer commit précédent) |
| Taille des déploiements | Énorme, risqué | Petit, incrémental |
| Stress du déploiement | Événement anxiogène | Opération routine |
| MTTR | Heures à jours | Minutes (Mean Time To Recovery) |
Rapport DORA : le rapport DevOps Research and Assessment mesure 4 métriques clés : fréquence de déploiement, délai de mise en production (lead time), taux d'échec des changements, et temps de récupération (MTTR). Les équipes "Elite" déploient plusieurs fois par jour avec un MTTR sous l'heure.
Git Flow
Git Flow (Vincent Driessen, 2010) est un workflow structuré avec des branches dédiées. Adapté aux projets avec des cycles de release définis, mais complexe pour le CI/CD moderne.
# Branches permanentes :
main → code en production (tags = releases)
develop → intégration des features
# Branches temporaires :
feature/xxx → nouvelle fonctionnalité
release/x.y → préparation de release (freeze)
hotfix/xxx → correction urgente en prod
# Workflow feature :
git checkout -b feature/panier develop
# ... développement ...
git checkout develop
git merge --no-ff feature/panier
git branch -d feature/panier
# Workflow hotfix :
git checkout -b hotfix/crash-login main
# ... correction ...
git checkout main
git merge --no-ff hotfix/crash-login
git tag -a v1.2.1
git checkout develop
git merge --no-ff hotfix/crash-login
git branch -d hotfix/crash-login
# Avantages : structuré, versioning clair
# Inconvénients : complexe, ralentit le CI/CD
# → Éviter pour les apps web déployées en continu
GitHub Flow — simple et efficace
GitHub Flow est un workflow minimaliste à une seule branche principale. Parfait pour le déploiement continu — chaque merge sur main peut être déployé automatiquement.
# 1. Créer une branche depuis main
git checkout main && git pull
git checkout -b feature/search-products
# 2. Développer et commiter régulièrement
git add -p
git commit -m "feat: add full-text search on products"
git push -u origin feature/search-products
# 3. Ouvrir une Pull Request
# → CI déclenche automatiquement : lint + tests
# → Reviewers assignés, discussion dans la PR
# 4. Code review + corrections si nécessaire
git commit -m "fix: handle empty search query"
git push # CI re-tourne automatiquement
# 5. Merge sur main (après approbation + CI verte)
# → Squash merge recommandé (un commit propre)
# 6. Déploiement automatique depuis main
# → Action déclenche le deploy sur prod
# Règle absolue : main est TOUJOURS déployable
Trunk-Based Development
# Les développeurs commitent sur main (trunk)
# plusieurs fois par jour.
# Les branches (si elles existent) durent < 1-2 jours.
# Avec feature flags (recommandé) :
# Du code incomplet peut être mergé mais désactivé
# config.py
FEATURES = {
"nouvelle_recherche": False, # en dev
"nouveau_checkout": True,
}
# Dans le code :
if FEATURES["nouvelle_recherche"]:
return nouvelle_recherche(query)
else:
return ancienne_recherche(query)
# Le flag est activé en prod quand c'est prêt
# Permet de déployer du code non terminé
# → Trunk toujours "green" et déployable
| Git Flow | GitHub Flow | Trunk-Based | |
|---|---|---|---|
| Branches | Multiples, longues | Feature branches courtes | Directement sur main |
| Complexité | Élevée | Faible | Très faible |
| CI/CD | Difficile | Bon | Idéal |
| Feature flags | Non | Optionnel | Recommandé |
| Convient à | Releases versionnées, libs | Apps web, SaaS | Google, Facebook, Netflix |
Pour un cours : commencer avec GitHub Flow — simple, compatible CI/CD, adapté aux projets scolaires. Git Flow pour comprendre le versioning sémantique. Trunk-based pour les grandes équipes matures.
Pull Requests & code review
## Titre : [type] description courte et précise
feat: add product search with filters
## Description :
### Contexte
Résout le ticket #142. Les utilisateurs doivent
pouvoir filtrer les produits par catégorie, prix
et disponibilité.
### Changements
- Ajout du composant `SearchFilters`
- Nouvelle route GET /api/products?q=&category=
- Index Elasticsearch sur `name` et `description`
- Tests unitaires SearchService (couverture 87%)
### Tests manuels
- [x] Recherche vide retourne tous les produits
- [x] Filtre catégorie fonctionne
- [x] Résultats triés par pertinence
- [x] Pagination OK
### Screenshots
[capture d'écran des filtres]
## Checklist PR :
✓ Titre clair avec type (feat/fix/docs/refactor)
✓ Description explique le POURQUOI, pas juste le QUOI
✓ Tests ajoutés / mis à jour
✓ CI verte avant de demander une review
✓ PR petite (< 400 lignes si possible)
✓ Lien vers le ticket / issue
# Pour le reviewer :
✓ Reviewer dans les 24h (ne pas bloquer l'équipe)
✓ Distinguer : bloquant / suggestion / question
✓ Critiquer le code, pas l'auteur
✓ Expliquer le pourquoi de chaque commentaire
✓ Proposer une alternative quand on rejette
# Niveaux de commentaires (GitHub) :
# [BLOQUANT] Ce bug doit être corrigé avant merge
# [nit] Nitpick — petite amélioration optionnelle
# [?] Question — je ne comprends pas
# [suggestion] Idée d'amélioration, non obligatoire
# Pour l'auteur :
✓ Répondre à chaque commentaire
✓ Expliquer les choix techniques
✓ Ne pas prendre les critiques personnellement
✓ "Resolved" ≠ ignoré, commenter ce qui a été fait
# PR Protection Rules (GitHub Settings) :
→ Require 1+ approving review before merge
→ Dismiss stale reviews when new commits pushed
→ Require status checks to pass (CI verte)
→ Require branches to be up to date
→ No direct push to main
Commits conventionnels
# Format : type(scope): description
# [corps optionnel]
# [footer optionnel]
# Types principaux :
feat → nouvelle fonctionnalité
fix → correction de bug
docs → documentation uniquement
style → formatage, pas de logique changée
refactor → restructuration sans bug fix ni feature
test → ajout ou modification de tests
chore → maintenance (dépendances, config)
perf → amélioration de performance
ci → changements au pipeline CI/CD
build → système de build, Dockerfile
# Exemples :
feat(auth): add OAuth Google login
fix(cart): prevent duplicate item addition
docs(api): document /users endpoint
refactor(search): extract SearchService class
test(auth): add unit tests for JWT validation
ci: add SonarQube analysis to pipeline
chore(deps): update React to 18.3
# Breaking change (BREAKING CHANGE dans footer) :
feat(api)!: rename /users to /accounts
BREAKING CHANGE: The /users endpoint has been
renamed to /accounts. Update all API clients.
# Avec des commits conventionnels, des outils comme
# semantic-release ou conventional-changelog
# génèrent automatiquement :
# 1. Le numéro de version (SemVer automatique) :
feat → MINOR (1.0.0 → 1.1.0)
fix → PATCH (1.1.0 → 1.1.1)
BREAKING → MAJOR (1.1.1 → 2.0.0)
# 2. Le CHANGELOG :
## [1.3.0] - 2024-03-15
### Features
- feat(auth): add OAuth Google login
- feat(cart): save cart to localStorage
### Bug Fixes
- fix(search): handle empty query gracefully
- fix(cart): prevent duplicate items
### Performance
- perf(images): add lazy loading
# 3. Les tags Git automatiques
# 4. La release GitHub avec notes de version
# Enforcer avec commitlint + husky :
# → Bloque les commits qui ne respectent pas le format
Concepts & vocabulaire
| Concept | Définition | Analogie |
|---|---|---|
| Workflow | Processus automatisé défini dans un fichier YAML (.github/workflows/) |
Une recette de cuisine complète |
| Event (on:) | Déclencheur du workflow (push, PR, schedule, manuel…) | Le minuteur de cuisson qui démarre |
| Job | Ensemble d'étapes qui s'exécutent sur le même runner | Un chef cuisinier |
| Step | Commande ou action individuelle dans un job | Une étape de la recette |
| Action | Unité réutilisable (uses: actions/checkout@v4) | Un ustensile spécialisé |
| Runner | Serveur qui exécute les jobs (ubuntu-latest, windows, macos) | La cuisine où on travaille |
| Artifact | Fichier généré et sauvegardé entre jobs (build output, rapports de tests) | Le plat résultant |
| Secret | Variable chiffrée (API key, token de déploiement) | La recette secrète du chef |
Syntaxe YAML — structure d'un workflow
# Nom affiché dans l'onglet Actions de GitHub
name: CI Pipeline
# Déclencheurs
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
# Variables d'environnement globales
env:
PYTHON_VERSION: '3.11'
NODE_VERSION: '20'
jobs:
# ── Job 1 : Lint ──────────────────────────
lint:
name: 🔍 Lint & Type Check
runs-on: ubuntu-latest # runner GitHub
steps:
# Récupérer le code
- uses: actions/checkout@v4
# Configurer Python
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
# Installer les dépendances
- name: Install dependencies
run: pip install flake8 mypy
# Exécuter le lint
- name: Run flake8
run: flake8 . --max-line-length=88
- name: Run mypy
run: mypy app/
# ── Job 2 : Tests ─────────────────────────
test:
name: 🧪 Tests
runs-on: ubuntu-latest
needs: lint # attend que lint soit vert
services: # conteneurs annexes
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install dependencies
run: pip install -r requirements.txt
- name: Run tests with coverage
run: pytest --cov=app --cov-report=xml
env:
DATABASE_URL: postgresql://postgres:test@localhost/testdb
# Uploader le rapport de couverture
- name: Upload coverage report
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
Déclencheurs (on:) — tous les événements
on:
# Push sur des branches ou tags spécifiques
push:
branches: [main, 'release/**']
tags: ['v*.*.*'] # v1.2.3
paths: ['src/**', 'Dockerfile']
paths-ignore: ['docs/**', '*.md']
# Pull Request
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Planifié (cron) — format standard Unix cron
schedule:
- cron: '0 2 * * 1' # Lundi à 2h du matin
- cron: '0 8 * * *' # Tous les jours à 8h
# Déclenchement manuel depuis l'UI GitHub
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: 'staging'
type: choice
options: [staging, production]
# Déclenché par un autre workflow
workflow_call:
workflow_run:
workflows: ['CI Pipeline']
types: [completed]
# Contextes disponibles dans les expressions ${{ }} :
# github — infos sur l'événement déclencheur
${{ github.ref }} # refs/heads/main
${{ github.sha }} # SHA du commit
${{ github.actor }} # nom du déclencheur
${{ github.event_name }} # push, pull_request...
${{ github.repository }} # owner/repo
# secrets — variables chiffrées
${{ secrets.DOCKER_TOKEN }}
${{ secrets.DEPLOY_KEY }}
# env — variables d'environnement
${{ env.PYTHON_VERSION }}
# Conditions (if:)
if: github.ref == 'refs/heads/main'
if: github.event_name == 'push'
if: ${{ failure() }} # si step précédent échoue
if: ${{ always() }} # toujours exécuter
if: contains(github.ref, 'release')
# Outputs entre steps
- name: Get version
id: version
run: echo "tag=$(git describe --tags)" >> $GITHUB_OUTPUT
- name: Use version
run: echo "Version is ${{ steps.version.outputs.tag }}"
Jobs, steps & strategy matrix
jobs:
lint: # démarre immédiatement
runs-on: ubuntu-latest
steps: [...]
test:
needs: lint # attend lint ✅
runs-on: ubuntu-latest
steps: [...]
build:
needs: lint # parallèle avec test !
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: [test, build] # attend LES DEUX
runs-on: ubuntu-latest
steps: [...]
# Graphe d'exécution :
# lint
# ├── test ─────┐
# └── build ────┴── deploy
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12']
os: [ubuntu-latest, windows-latest]
fail-fast: false # ne pas stopper si 1 échoue
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
# Utiliser la valeur de la matrice
python-version: ${{ matrix.python-version }}
- run: pytest
# Crée 6 jobs en parallèle :
# Python 3.10 / Ubuntu
# Python 3.10 / Windows
# Python 3.11 / Ubuntu
# Python 3.11 / Windows
# Python 3.12 / Ubuntu
# Python 3.12 / Windows
# Passer des artefacts entre jobs :
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
- name: Download artifact (autre job)
uses: actions/download-artifact@v4
with:
name: build-output
Secrets & environnements
# ✗ NE JAMAIS faire ça :
- run: docker login -u monlogin -p MonSuperMotDePasse123
# ✓ Utiliser les secrets GitHub :
# Settings → Secrets and variables → Actions → New secret
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Variables d'environnement vs Secrets :
# Variables : valeurs non sensibles (URL, flags)
# Secrets : valeurs sensibles (tokens, clés API)
# Les secrets sont masqués dans les logs :
# *** au lieu de la valeur réelle
# Portées des secrets :
# Repository : disponibles pour ce repo
# Organisation : partagés entre repos
# Environnement : staging vs production différents
# Settings → Environments → New environment
# Environnement "production" avec :
# - Required reviewers : 2 personnes doivent approuver
# - Wait timer : 5 minutes avant déploiement
# - Allowed branches : main seulement
deploy-production:
runs-on: ubuntu-latest
needs: deploy-staging
environment:
name: production
url: https://mon-app.com
steps:
- name: Deploy to production
run: ./deploy.sh
env:
# Secret spécifique à l'environnement production
DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}
API_URL: ${{ vars.PROD_API_URL }}
# Flux de déploiement multi-environnement :
# main → deploy staging (auto)
# → tests E2E (auto)
# → approbation humaine (review required)
# → deploy production (après approbation)
Workflows complets — exemples réels
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: '3.11', cache: pip }
- run: pip install -r requirements.txt
- name: Lint
run: |
flake8 . --max-line-length=88
black --check .
isort --check .
- name: Tests
run: pytest --cov=app --cov-fail-under=80
docker:
needs: ci
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Build & push Docker image
uses: docker/build-push-action@v5
with:
push: true
tags: monlogin/mon-app:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
name: Release
on:
push:
tags: ['v*.*.*']
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write # créer la release
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # pour le changelog
- name: Build
run: python -m build
# Créer la GitHub Release automatiquement
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
# Génère les notes de version depuis les commits
generate_release_notes: true
files: dist/*
token: ${{ secrets.GITHUB_TOKEN }}
# Publier sur PyPI
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
Conteneurs vs machines virtuelles
| 🖥️ VM | 🐳 Conteneur | |
|---|---|---|
| Isolation | OS complet virtualisé | Processus isolé (namespaces + cgroups) |
| Taille | Go (OS inclus) | Mo (partage le noyau hôte) |
| Démarrage | Minutes | Secondes (parfois millisecondes) |
| Densité | Quelques VMs / hôte | Dizaines à centaines / hôte |
| Portabilité | Image lourde | Image légère, tourne partout |
| Isolation sécurité | Forte (OS séparé) | Bonne (partage noyau — surface d'attaque) |
| Idéal pour | Isolation forte, OS différents | Microservices, CI/CD, scale horizontal |
# Image : template immuable en couches (read-only)
# Conteneur : instance en cours d'exécution d'une image
# Registry : dépôt d'images (Docker Hub, GHCR, ECR)
# Dockerfile : recette pour construire une image
# Volume : stockage persistant hors conteneur
# Network : réseau virtuel entre conteneurs
# Analogie :
Image ≈ classe Python (blueprint)
Conteneur ≈ instance (objet créé depuis la classe)
Registry ≈ PyPI (dépôt d'images publiques)
# Pourquoi Docker pour CI/CD :
→ "Ça marche sur ma machine" → plus jamais !
→ Build une fois, déploie partout
→ Environnements identiques dev/staging/prod
→ Isolation des dépendances
→ Rollback instantané (re-déployer image précédente)
→ Scale horizontal trivial
Dockerfile — construire une image
# ── Multi-stage build ─────────────────────
# Stage 1 : build (lourd, outils de build)
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2 : production (léger, sans outils de build)
FROM python:3.11-slim
WORKDIR /app
# Copier seulement ce qui est nécessaire
COPY --from=builder /root/.local /root/.local
COPY . .
# Utilisateur non-root (sécurité !)
RUN adduser --disabled-password --gecos "" appuser
USER appuser
# Métadonnées
LABEL maintainer="equipe@exemple.com"
LABEL version="1.0"
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# Comme .gitignore — ne pas copier dans l'image
.git
.github
.gitignore
__pycache__
*.pyc
*.pyo
.pytest_cache
.coverage
htmlcov/
*.egg-info
.venv
venv/
node_modules/
.env # IMPORTANT — ne jamais inclure les secrets
.env.local
*.log
Dockerfile
docker-compose*.yml
README.md
docs/
Ordre des instructions = performance : les couches qui changent rarement passent en premier (dépendances). Le code source passe en dernier. Docker cache les couches inchangées → builds 10× plus rapides.
Ne jamais mettre de secrets dans un Dockerfile (ARG MY_KEY=secret reste visible dans l'historique de l'image). Utiliser des variables d'environnement à l'exécution ou Docker secrets.
Commandes Docker essentielles
# Construire une image depuis le Dockerfile
docker build -t mon-app:1.0 .
docker build -t mon-app:latest -f Dockerfile.prod .
# Lister les images locales
docker images
docker image ls
# Supprimer une image
docker rmi mon-app:1.0
docker image prune # supprimer les images orphelines
# Tagger et pousser sur Docker Hub
docker tag mon-app:latest monlogin/mon-app:latest
docker push monlogin/mon-app:latest
# Pousser sur GitHub Container Registry
docker tag mon-app:latest ghcr.io/owner/mon-app:latest
docker push ghcr.io/owner/mon-app:latest
# Inspecter une image (couches, metadata)
docker inspect mon-app:latest
docker history mon-app:latest # voir les couches
# Démarrer un conteneur
docker run mon-app:latest
# Options courantes :
docker run \
-d \ # détaché (background)
-p 8080:8000 \ # hôte:conteneur
-e DATABASE_URL=postgres://... \ # variable env
-v ./data:/app/data \ # volume monté
--name mon-api \ # nommer le conteneur
--rm \ # supprimer à l'arrêt
--network mon-réseau \ # réseau Docker
mon-app:latest
# Gestion des conteneurs
docker ps # conteneurs actifs
docker ps -a # tous les conteneurs
docker stop mon-api
docker start mon-api
docker restart mon-api
docker rm mon-api # supprimer le conteneur
# Debug
docker logs mon-api # voir les logs
docker logs -f mon-api # suivre en temps réel
docker exec -it mon-api bash # entrer dans le conteneur
docker exec mon-api python -c "print('ok')"
Docker Compose — orchestration locale
services:
api:
build: . # build depuis Dockerfile local
ports: ["8000:8000"]
environment:
DATABASE_URL: postgresql://user:pass@db:5432/mydb
REDIS_URL: redis://redis:6379
depends_on:
db:
condition: service_healthy
volumes:
- .:/app # rechargement à chaud en dev
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready"]
interval: 10s
retries: 5
redis:
image: redis:7-alpine
ports: ["6379:6379"]
volumes:
postgres_data:
# Démarrer toute la stack
docker compose up -d
# Rebuilder si Dockerfile modifié
docker compose up -d --build
# Arrêter et supprimer les conteneurs
docker compose down
# Supprimer aussi les volumes (données !)
docker compose down -v
# Voir les logs de tous les services
docker compose logs -f
# Logs d'un service spécifique
docker compose logs -f api
# Exécuter une commande dans un service
docker compose exec api bash
docker compose exec db psql -U user mydb
# Voir l'état des services
docker compose ps
# Override pour CI/CD (docker-compose.ci.yml)
docker compose -f docker-compose.yml \
-f docker-compose.ci.yml \
up --exit-code-from tests
Docker dans le pipeline CI/CD
name: Docker CI/CD
on:
push:
branches: [main]
tags: ['v*.*.*']
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # push sur GHCR
steps:
- uses: actions/checkout@v4
# Métadonnées automatiques (tags, labels)
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=sha,prefix=sha-
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
# Cache Docker layers entre builds
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Login sur GitHub Container Registry
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Build et push avec cache
- name: Build and push
uses: docker/build-push-action@v5
with:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Stratégie de tagging recommandée :
mon-app:latest ← dernier commit sur main
mon-app:1.3.0 ← version sémantique
mon-app:sha-a3f2c1b ← SHA du commit (immuable)
mon-app:main ← branche
# Environnements et images :
┌────────────────────────────────────────────┐
│ Développeur push sur main │
│ → CI build image mon-app:sha-abc123 │
│ → Tests passent │
│ → Deploy staging (image sha-abc123) │
│ → Approbation humaine │
│ → Tag v1.3.0 → image mon-app:1.3.0 │
│ → Deploy production (image v1.3.0) │
└────────────────────────────────────────────┘
# Rollback instantané :
# Bug en prod → re-déployer l'image précédente
docker pull mon-app:1.2.9
docker run ... mon-app:1.2.9
Scan de sécurité dans le pipeline : utiliser trivy ou grype pour scanner les images avant de les déployer. Une vulnérabilité critique dans une dépendance peut être détectée et bloquée automatiquement.
Cheat sheet CI/CD & Docker
Git Workflows
| Git Flow | Releases versionnées, branches longues |
| GitHub Flow | PR courtes → merge main → deploy |
| Trunk-based | Commit sur main + feature flags |
| PR rules | CI verte + 1 review avant merge |
| Conventional commits | type(scope): description |
GitHub Actions
| Fichier | .github/workflows/*.yml |
| Déclencheur | on: push / pull_request / schedule |
| Contexte | ${{ github.sha }}, ${{ secrets.X }} |
| Dépendance | needs: [job1, job2] |
| Matrix | strategy.matrix: {version: [...]} |
| Cache | cache-from/to: type=gha |
Docker — commandes clés
| docker build -t app:v1 . | Construire |
| docker run -d -p 8080:8000 | Démarrer |
| docker exec -it app bash | Entrer |
| docker logs -f app | Voir les logs |
| docker compose up -d | Stack locale |
| docker push registry/app:tag | Publier |
Bonnes pratiques
| Fail fast | Lint avant tests avant build |
| Cache layers | COPY requirements.txt avant COPY . |
| Multi-stage | Builder séparé → image finale légère |
| Non-root | USER appuser dans Dockerfile |
| Secrets | Jamais dans image, toujours en env |
| .dockerignore | Exclure .git, .env, node_modules |