DevOps

CI/CD
& Docker

Intégration et déploiement continus — pipelines, Git workflows, GitHub Actions et conteneurs Docker.

CI / CD / CD — les trois notions

SigleNomDé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.
Avant CI/CD — le chaos de l'intégration
# 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

📝
Source
git push
PR ouverte
🔨
Build
compile
lint
type-check
🧪
Tests
unit tests
integration
couverture
🔍
Qualité
SAST
SonarQube
dépendances
📦
Package
Docker build
artefact
registry
🚀
Deploy Staging
auto-deploy
smoke tests
E2E tests
Deploy Prod
manuel (CD)
auto (CDepl)
monitoring
ÉtapeButÉ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 ?

1
Détection rapide des régressions — un bug introduit aujourd'hui est trouvé aujourd'hui, pas dans 3 semaines lors du "merge day".
2
Déploiements plus fréquents et moins risqués — déployer souvent des petits changements est bien plus sûr qu'un gros déploiement mensuel.
3
Réduction du risque humain — les étapes manuelles répétitives (build, deploy, tag) sont sources d'erreur. Les automatiser les élimine.
4
Documentation vivante — le pipeline est une documentation exécutable de comment le projet est construit et déployé.
5
Confiance et vélocité accrues — quand les tests sont fiables, l'équipe ose changer le code. Sans CI, les développeurs évitent de toucher aux zones sensibles.
Sans CI/CDAvec CI/CD
Fréquence déploiementMensuel / trimestrielPluriquotidien
Délai détection bugSemainesMinutes
Temps de rollbackHeuresMinutes (re-déployer commit précédent)
Taille des déploiementsÉnorme, risquéPetit, incrémental
Stress du déploiementÉvénement anxiogèneOpération routine
MTTRHeures à joursMinutes (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.

── Git Flow — branches principales ──────────
main (production)
develop
feature/login
hotfix/crash-prod
Git Flow — commandes
# 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.

── GitHub Flow ───────────────────────────────
main → prod
feature/search (PR → review → merge)
fix/typo-header (PR courte)
GitHub Flow — les 6 étapes
# 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

Principe — branches très courtes
# 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 FlowGitHub FlowTrunk-Based
BranchesMultiples, longuesFeature branches courtesDirectement sur main
ComplexitéÉlevéeFaibleTrès faible
CI/CDDifficileBonIdéal
Feature flagsNonOptionnelRecommandé
Convient àReleases versionnées, libsApps web, SaaSGoogle, 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

Anatomie d'une bonne PR
## 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
Code review — bonnes pratiques
# 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 — Conventional Commits
# 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.
Bénéfices — CHANGELOG automatique
# 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

ConceptDéfinitionAnalogie
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
⚙️ ci.yml on: push, pull_request
🔍
lint passed 45s
checkout → setup-python → flake8 → mypy
↓ needs: lint
🧪
test passed 2m14s
checkout → setup-python → install → pytest --cov
↓ needs: test (only on main)
📦
build-push passed 1m32s
docker build → docker push → update manifest
↓ needs: build-push
🚀
deploy running
kubectl rollout → wait → smoke-test

Syntaxe YAML — structure d'un workflow

.github/workflows/ci.yml — structure annotée
# 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/
suite — job test avec dépendance
  # ── 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

Événements courants
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 & expressions
# 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

Dépendances entre jobs & parallélisme
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
Strategy matrix — tester sur plusieurs versions
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

Secrets — ne jamais hardcoder
# ✗ 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
Environnements — protection de la prod
# 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

CI Python complet — pytest + Docker + deploy
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
Release automatique sur tag v*
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
IsolationOS complet virtualiséProcessus isolé (namespaces + cgroups)
TailleGo (OS inclus)Mo (partage le noyau hôte)
DémarrageMinutesSecondes (parfois millisecondes)
DensitéQuelques VMs / hôteDizaines à centaines / hôte
PortabilitéImage lourdeImage légère, tourne partout
Isolation sécuritéForte (OS séparé)Bonne (partage noyau — surface d'attaque)
Idéal pourIsolation forte, OS différentsMicroservices, CI/CD, scale horizontal
Concepts clés Docker
# 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

FROM python:3.11-slimbase image
WORKDIR /apprépertoire de travail
COPY requirements.txt .↑ couche cachée si inchangée
RUN pip install -r requirements.txt↑ couche cachée — dépendances
COPY . .code source (souvent modifié)
EXPOSE 8000documentation du port
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]commande par défaut
Dockerfile Python production — best practices
# ── 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"]
.dockerignore — exclure les fichiers inutiles
# 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

Images
# 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
Conteneurs
# 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

docker-compose.yml — stack complète
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:
Commandes Docker Compose
# 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

GitHub Actions — build, test et push Docker
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 tags d'image
# 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 FlowReleases versionnées, branches longues
GitHub FlowPR courtes → merge main → deploy
Trunk-basedCommit sur main + feature flags
PR rulesCI verte + 1 review avant merge
Conventional commitstype(scope): description

GitHub Actions

Fichier.github/workflows/*.yml
Déclencheuron: push / pull_request / schedule
Contexte${{ github.sha }}, ${{ secrets.X }}
Dépendanceneeds: [job1, job2]
Matrixstrategy.matrix: {version: [...]}
Cachecache-from/to: type=gha

Docker — commandes clés

docker build -t app:v1 .Construire
docker run -d -p 8080:8000Démarrer
docker exec -it app bashEntrer
docker logs -f appVoir les logs
docker compose up -dStack locale
docker push registry/app:tagPublier

Bonnes pratiques

Fail fastLint avant tests avant build
Cache layersCOPY requirements.txt avant COPY .
Multi-stageBuilder séparé → image finale légère
Non-rootUSER appuser dans Dockerfile
SecretsJamais dans image, toujours en env
.dockerignoreExclure .git, .env, node_modules