Kelenix · Docker + IA
Kelenix Tech Formation professionnelle
Guide de formation · Édition 2026

Docker + IA

De Zéro à Professionnel

Maîtriser Docker, conteneuriser ses applications et construire des applications modernes intégrant l'Intelligence Artificielle — du tout premier docker run hello-world jusqu'à un assistant documentaire complet orchestré avec Docker Compose.

Débutant → Développeur → Pro Docker & Compose v2 API IA · RAG · IA locale · GPU 33 chapitres · 4 projets
Préface

Un accompagnement, pas une documentation

Ce livre n'est pas une documentation technique de plus. C'est une formation complète, pensée pour vous prendre par la main à l'endroit exact où vous êtes aujourd'hui — même si vous n'avez jamais tapé une seule commande Docker — et vous conduire, étape après étape, jusqu'à concevoir seul des architectures d'applications modernes intégrant de l'intelligence artificielle.

La plupart des ressources sur Docker souffrent du même défaut : elles expliquent comment faire sans jamais expliquer pourquoi. On vous demande d'écrire un Dockerfile avant même d'avoir compris ce qu'est une image. On vous jette dans docker compose up sans que vous ayez saisi le problème que Compose résout. Résultat : vous copiez des commandes sans les comprendre, et le jour où quelque chose casse — et quelque chose casse toujours — vous êtes bloqué.

Ici, la démarche est inverse. Chaque concept suit une progression rigoureuse :

Comprendre Voir Faire Modifier Résoudre Construire

On comprend d'abord le problème réel. On voit le concept à travers une analogie et un schéma. On fait soi-même, en tapant les commandes. On modifie pour tester les limites. On résout les erreurs les plus fréquentes — parce qu'elles arriveront. Et enfin, on construit de vrais projets.

Le livre est traversé par deux grands axes. Le premier, le plus long, fait de vous un utilisateur professionnel de Docker. Le second vous apprend à mettre Docker au service de l'IA : connecter une application conteneurisée à une API de modèle de langage, comprendre et bâtir un système RAG, exécuter un modèle localement, exploiter un GPU. À la fin, ces deux axes se rejoignent dans un projet final ambitieux : un assistant documentaire intelligent, entièrement conteneurisé.

Notre promesse

À la dernière page, vous ne vous demanderez plus « est-ce que je saurais refaire ça tout seul ? ». Vous saurez. Parce que vous l'aurez déjà fait, plusieurs fois, avec vos mains.

Mode d'emploi

Comment utiliser ce livre

Ce livre se pratique autant qu'il se lit. Voici comment en tirer le maximum.

Lisez-le dans l'ordre (au moins la première fois)

La difficulté augmente volontairement et progressivement. Chaque chapitre suppose acquis ce qui précède. Si vous êtes déjà à l'aise avec les bases, vous pouvez survoler les premières parties — mais ne sautez jamais un concept que vous n'avez jamais pratiqué.

Gardez un terminal ouvert

Chaque commande présentée est faite pour être tapée, pas seulement lue. La mémoire des doigts vaut dix relectures. Résistez à la tentation du copier-coller sur les premières commandes.

Repérez les encadrés

Ils rythment la lecture et signalent l'essentiel. Voici la légende que vous retrouverez tout au long du livre :

Important

Un point clé à retenir absolument. Si vous ne deviez retenir qu'une chose de la section, c'est ici.

Attention

Un piège classique, une confusion fréquente, ou une manipulation qui peut mal tourner.

Bonne pratique

La façon professionnelle de faire, celle que vous retrouverez dans les vraies équipes.

Erreur fréquente

Un message d'erreur ou un comportement qui bloque beaucoup de débutants — et comment le diagnostiquer.

Astuce

Un raccourci, une commande pratique, un détail qui fait gagner du temps.

Challenge

Un défi supplémentaire, sans solution immédiate, pour vérifier que vous êtes vraiment autonome.

Faites les exercices — et cherchez avant de regarder la correction

Chaque grande partie propose des exercices à trois niveaux : Débutant (application directe), Intermédiaire (combiner plusieurs concepts), Pro (problème ouvert). Les corrections sont masquées : cliquez pour les révéler, mais seulement après avoir vraiment essayé.

Quand ça ne marche pas

Vous croiserez régulièrement une rubrique « Si ça ne fonctionne pas » structurée en Symptôme → Cause probable → Vérification → Solution. Elle vaut de l'or : déboguer est une compétence à part entière, et ce livre vous l'enseigne autant que Docker lui-même.

Convention de commandes

Nous utilisons partout la syntaxe moderne docker compose (Compose V2, intégré à Docker) et non l'ancienne docker-compose. Les blocs de code portent parfois une étiquette (fichier ou type de shell) au-dessus d'eux.

Avant de commencer

Prérequis

La bonne nouvelle : ils sont volontairement légers. Ce livre part vraiment de zéro sur Docker.

Ce dont vous avez besoin

  • Un ordinateur sous Windows, macOS ou Linux, sur lequel vous pouvez installer des logiciels.
  • Des bases générales en informatique : savoir ouvrir un terminal, naviguer dans des dossiers, éditer un fichier texte.
  • De la curiosité et un peu de rigueur. C'est de loin le prérequis le plus important.
  • Une connexion Internet pour télécharger Docker et les images.

Ce qui n'est pas requis

  • Aucune connaissance préalable de Docker ou des conteneurs.
  • Aucune expertise en DevOps, Cloud ou administration système.
  • Pas besoin d'être expert d'un langage : les exemples touchent Java, Node.js et Python, mais chaque étape est expliquée.
  • Aucune notion de mathématiques de l'IA. On explique le RAG et les modèles sans une seule formule.
Bonne pratique dès maintenant

Créez un dossier docker-formation/ quelque part sur votre machine. Chaque chapitre pratique et chaque projet y aura son sous-dossier. Vous construirez ainsi, au fil du livre, votre propre bibliothèque d'exemples réutilisables.

Un mot sur les niveaux de lecture

Le livre est écrit pour trois profils, et vous progresserez naturellement de l'un à l'autre :

NiveauQuiObjectif à la sortie
DébutantN'a jamais utilisé Docker.Comprendre les conteneurs et lancer ses premières applications.
DéveloppeurConnaît un langage (Java, Python, JS/TS, PHP) mais pas Docker.Conteneuriser ses propres applications et les orchestrer avec Compose.
ProfessionnelVeut utiliser Docker en équipe et en production.Sécurité, CI/CD, IA, RAG, GPU — et concevoir des architectures seul.
Plan complet de la formation

Sommaire détaillé

Voici l'intégralité du parcours. Il est conçu comme une montée en difficulté continue : chaque partie prépare la suivante, et les deux axes — Docker puis IA — convergent dans le projet final.

Où en est la rédaction

Cette version couvre le front matter complet, ce sommaire détaillé, et les Parties 0 et 1 entièrement rédigées. Les parties suivantes seront ajoutées dans ce même document, dans l'ordre ci-dessous, après validation du ton et du niveau.

flowchart TD
  A([Débutant complet]) --> B[Bases & conteneurs]
  B --> C[Images & Dockerfile]
  C --> D[Vraies applications]
  D --> E[Réseaux · Volumes · Config]
  E --> F[Docker Compose]
  F --> G[Debugging · Optimisation · Sécurité]
  G --> H[Registry · CI/CD · Déploiement]
  H --> I[Docker + IA]
  I --> J[RAG & base vectorielle]
  J --> K[IA locale & GPU]
  K --> L[Projets de fin de formation]
  L --> M([Développeur d'applications IA Dockerisées])
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
La trajectoire globale du livre, du premier conteneur à l'autonomie professionnelle.

Front matter

  • Couverture · Préface · Comment utiliser ce livre · Prérequis · Sommaire détaillé

Partie 0 — Introduction

  • Chapitre 0 — Pourquoi Docker en 2026 ? Les problèmes du développement sans conteneurs, le fameux « ça marche sur ma machine », Docker dans les entreprises modernes, le Cloud, le CI/CD, les microservices et l'IA.

Partie 1 — Comprendre les fondamentaux

  • Chapitre 1 — Le problème que Docker résout. Application, OS, dépendances, versions, environnement, configuration ; ce qui casse quand on déplace une application.
  • Chapitre 2 — Comprendre les conteneurs. Conteneur, image, runtime, isolation, portabilité ; conteneur vs machine virtuelle, expliqué visuellement.

Partie 2 — Installation et premiers pas

  • Chapitre 3 — Installer Docker. Docker Desktop vs Docker Engine, Windows/macOS/Linux, vérifier l'installation, docker run hello-world décortiqué.
  • Chapitre 4 — Les premières commandes. run, ps, stop/start/restart, rm, images, rmi, logs, inspect, exec ; tableau récapitulatif. Mini-projet 1 : nginx + redis + postgres.

Partie 3 — Images Docker

  • Chapitre 5 — Comprendre les images. Image, couche, repository, tag, registry, Docker Hub.
  • Chapitre 6 — Créer sa première image. FROM, WORKDIR, COPY, RUN, EXPOSE, CMD, ENTRYPOINT, ENV, ligne par ligne.
  • Chapitre 7 — Dockerfile professionnel. Ordre des instructions, cache, .dockerignore, images légères (Alpine, Debian slim, officielles), sécurité.

Partie 4 — Conteneuriser de vraies applications

  • Chapitre 8 — Docker + Java (Spring Boot) + PostgreSQL.
  • Chapitre 9 — Docker + Node.js. API, ports, environnement, dépendances, npm, logs.
  • Chapitre 10 — Docker + Python (FastAPI).
  • Chapitre 11 — Docker + frontend. Build, fichiers statiques, Nginx, multi-stage.

Partie 5 — Réseaux Docker

  • Chapitre 12 — Comprendre les réseaux. Réseau bridge, communication entre conteneurs, hostname, ports ; pourquoi postgres:5432 et non localhost:5432.

Partie 6 — Stockage

  • Chapitre 13 — Volumes Docker. Pourquoi les données disparaissent, volume vs bind mount, persistance, exemple PostgreSQL.

Partie 7 — Variables et configuration

  • Chapitre 14 — Variables d'environnement. -e, fichier .env, configuration, credentials, secrets, dev/test/prod — et pourquoi jamais de mot de passe dans un Dockerfile.

Partie 8 — Docker Compose

  • Chapitre 15 — Pourquoi Docker Compose ? Services, networks, volumes, ports, environment, depends_on, healthcheck.
  • Chapitre 16 — Projet complet avec Compose. Frontend → Backend REST → PostgreSQL (+ Redis si pertinent).

Partie 9 — Debugging et problèmes réels

  • Chapitre 17 — Déboguer Docker. Port déjà utilisé, conteneur qui s'arrête, mauvaise variable, connexion PostgreSQL impossible, hostname, image introuvable, permission denied, volume, réseau. La méthode « Diagnostic en 5 minutes ».

Partie 10 — Docker avancé

  • Chapitre 18 — Multi-stage builds. Mauvaise vs bonne approche, bénéfices (taille, sécurité, performance).
  • Chapitre 19 — Optimisation des images. Cache de couches, ordre, .dockerignore, images minimales, avant/après.
  • Chapitre 20 — Sécurité Docker. Non-root, images officielles, mises à jour, secrets, surface d'attaque, scan, moindre privilège.

Partie 11 — Docker en équipe et en production

  • Chapitre 21 — Docker Hub et Registry. login, tag, push, pull, registry privé.
  • Chapitre 22 — Docker et CI/CD. Git → build → tests → push → deploy ; pipeline GitHub Actions.
  • Chapitre 23 — Docker et déploiement. Serveur/VPS, registry, reverse proxy, HTTPS, variables, logs ; où Kubernetes s'insère (survol).

Partie 12 — Introduction à l'IA Axe IA

  • Chapitre 24 — Pourquoi Docker + IA ? Dépendances lourdes, Python, modèles, versions, GPU, environnement reproductible.

Partie 13 — Première application IA conteneurisée

  • Chapitre 25 — API IA avec Python + FastAPI + Docker.

Partie 14 — Intégration des API d'IA

  • Chapitre 26 — Connecter une application Docker à une API d'IA. Clé API, variables d'environnement, sécurité, requêtes/réponses, gestion d'erreurs, coûts — sans dépendre d'un seul fournisseur.

Partie 15 — RAG et base vectorielle

  • Chapitre 27 — Comprendre le RAG. Embeddings, base vectorielle, chunks, retrieval, contexte — sans mathématiques inutiles.
  • Chapitre 28 — Projet RAG Dockerisé. « Chatbot documentaire » : frontend, backend, base vectorielle, modèle d'embedding, LLM.

Partie 16 — IA locale et modèles open source

  • Chapitre 29 — Exécuter un modèle IA localement. Inference, CPU/GPU, mémoire, quantization ; Ollama et Docker Model Runner ; limites matérielles.

Partie 17 — Docker + GPU

  • Chapitre 30 — Comprendre Docker et GPU. GPU, CUDA, NVIDIA Container Toolkit, accès GPU depuis un conteneur ; exemple GPU + alternative CPU.

Partie 18 — Projets de fin de formation

  • Projet 1 Débutant — « Mon premier environnement Docker » : application + PostgreSQL, volume, réseau, variables.
  • Projet 2 Intermédiaire — « Application Web complète » : Frontend + Backend + PostgreSQL + Redis avec Compose.
  • Projet 3 Avancé — « API IA Dockerisée » : génération de texte, historique, erreurs, variables, logs.
  • Projet 4 — PROJET FINAL — « AI Knowledge Assistant » : upload de documents, extraction, embeddings, recherche vectorielle, génération, interface web, backend, base de données, base vectorielle, Compose.

Partie 19 — Autonomie professionnelle

  • Chapitre 31 — Démarrer un projet Docker de zéro. Checklist en 12 étapes.
  • Chapitre 32 — Checklist Docker professionnelle. Développement, sécurité, performance, production, IA.

Partie 20 — Référence rapide

  • Cheat sheets : Commandes essentielles, Dockerfile, Docker Compose, Debugging, Docker + IA.
  • Glossaire : Container, Image, Dockerfile, Registry, Volume, Network, Compose, Layer, Build, Runtime, API, LLM, Embedding, RAG, Base vectorielle, Inference, GPU.
  • Évaluation finale et projet d'autonomie sans solution.
0
Partie 0

Introduction

Avant de taper la moindre commande, prenons cinq minutes pour comprendre pourquoi Docker existe et pourquoi, en 2026, il est devenu incontournable. Comprendre le « pourquoi » rend tout le reste évident.

Partie 0 · Chapitre 0

Pourquoi Docker en 2026 ?

À la fin de ce chapitre, vous serez capable de…
  • expliquer avec vos propres mots le problème que Docker résout ;
  • comprendre pourquoi « ça marche sur ma machine » est un vrai problème d'ingénierie ;
  • situer Docker dans le paysage moderne : Cloud, CI/CD, microservices et IA ;
  • savoir pourquoi vous apprenez Docker, et pas seulement comment.

Le problème

Imaginez la scène, vécue par des millions de développeurs. Vous passez deux semaines à construire une application. Sur votre ordinateur, tout fonctionne parfaitement. Vous l'envoyez à un collègue, ou vous la déployez sur un serveur… et rien ne marche. Une bibliothèque manque. La version de Python n'est pas la bonne. Une variable de configuration est absente. Le message d'erreur ne ressemble à rien de ce que vous avez vu.

Vous répondez alors la phrase la plus célèbre — et la plus détestée — de l'informatique :

La phrase interdite

« Pourtant… ça fonctionne sur ma machine. »

Le problème, c'est que votre machine ne part pas en production avec l'application. Ce qui marche chez vous et échoue ailleurs, c'est presque toujours une histoire d'environnement.

Le concept : votre application ne vit pas seule

Une application n'est jamais un simple fichier isolé. Pour fonctionner, elle a besoin d'un environnement entier autour d'elle :

  • un langage et sa version précise (Python 3.12, Node 22, Java 21…) ;
  • des dépendances — les bibliothèques externes qu'elle utilise, chacune dans une version compatible ;
  • des outils système installés sur la machine ;
  • une configuration : variables d'environnement, chemins, ports, adresses de bases de données ;
  • un système d'exploitation qui gère tout cela.

Sur votre machine, cet environnement s'est construit petit à petit, souvent sans que vous en gardiez la trace. Le jour où l'application change de machine, tout cet échafaudage invisible disparaît — et l'application s'écroule.

L'idée centrale de Docker en une phrase

Et si, au lieu de transporter seulement l'application, on transportait l'application avec tout son environnement, emballés ensemble dans une boîte scellée qui fonctionne à l'identique partout ? Cette boîte, c'est un conteneur. Le fabriquer et le faire tourner, c'est le travail de Docker.

Visualisation : l'analogie du conteneur maritime

Le nom « Docker » et sa fameuse baleine ne sont pas un hasard. Avant les conteneurs maritimes standardisés, charger un bateau était un cauchemar : chaque marchandise avait sa forme, son emballage, ses contraintes. L'invention du conteneur standard a tout changé : peu importe ce qu'il y a dedans (des bananes ou des voitures), la boîte a toujours la même taille, et n'importe quel port, camion ou grue dans le monde sait la manipuler.

Docker fait exactement cela pour le logiciel. Peu importe ce qu'il y a dans le conteneur — une API Java, un site Node.js, un modèle d'IA en Python — l'extérieur est standardisé. N'importe quelle machine équipée de Docker sait le faire tourner, à l'identique.

flowchart LR
  subgraph SANS [" Sans Docker"]
    A1[Application] -.dépend de.-> B1[Environnement<br/>de VOTRE machine]
    B1 -.absent ailleurs.-> C1(( Casse<br/>sur le serveur))
  end
  subgraph AVEC [" Avec Docker"]
    A2[Application] --- B2[Son environnement]
    A2 --- B2
    B2 --> D2[ Conteneur scellé]
    D2 --> E2((Tourne à l'identique<br/>partout))
  end
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
  style SANS fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
  style AVEC fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
Sans Docker, l'environnement reste sur votre machine. Avec Docker, il voyage avec l'application.

Pourquoi c'est devenu incontournable en 2026

Docker n'est pas une curiosité de développeur : c'est aujourd'hui un standard de l'industrie. Voici les cinq forces qui l'ont rendu indispensable — chacune deviendra un chapitre pratique plus loin.

Contexte moderneCe que Docker apporte
Le CloudLes serveurs Cloud sont éphémères et interchangeables. Un conteneur, portable par nature, s'y déploie à l'identique — de votre portable au serveur, sans surprise.
Le CI/CDLes chaînes d'intégration et de déploiement continus construisent, testent et livrent automatiquement. Elles s'appuient sur des conteneurs pour garantir que « testé » = « déployé », au bit près.
Les microservicesLes applications modernes se découpent en petits services indépendants. Chacun vit dans son conteneur, avec ses propres dépendances, sans se marcher dessus.
Le travail en équipeUn nouveau développeur lance une commande et obtient exactement le même environnement que toute l'équipe, en minutes au lieu de jours de configuration.
L'IALes applications d'IA cumulent des dépendances lourdes, des versions capricieuses, des modèles volumineux et parfois un GPU. Docker rend cet environnement fragile reproductible — c'est tout le second axe de ce livre.
Docker et l'IA : un mariage naturel

Si vous avez déjà tenté d'installer une bibliothèque d'IA en Python, vous connaissez l'enfer des versions incompatibles. Docker fige tout cet environnement une fois pour toutes. C'est précisément pour cela qu'apprendre Docker avant de se lancer dans l'IA appliquée est un choix stratégique — et le fil conducteur de cette formation.

Ce que Docker n'est pas (pour éviter les malentendus)

Deux clarifications utiles dès maintenant, que nous approfondirons :

  • Docker n'est pas une machine virtuelle. Un conteneur est bien plus léger et démarre en une fraction de seconde. Nous verrons pourquoi au Chapitre 2.
  • Docker ne remplace pas votre code. Il l'emballe. Votre application reste la vôtre ; Docker s'occupe de la rendre transportable et reproductible.
Erreur de débutant à éviter dès le départ

Beaucoup de débutants apprennent des commandes Docker par cœur sans comprendre à quoi elles servent. Ils sont perdus dès que la situation dévie du tutoriel. Dans ce livre, on inverse : on comprend le problème, puis la commande devient une évidence. Prenez ce chapitre au sérieux — c'est la fondation de tout le reste.

Exercices

Exercice 0.1 Débutant · Réflexion

Sans utiliser Docker, décrivez par écrit tout ce qu'un collègue devrait installer et configurer sur son ordinateur pour faire tourner une application que vous connaissez (un site web, un script, un projet d'école). Listez au moins cinq éléments.

Voir la correction

Il n'y a pas de réponse unique, mais une bonne liste ressemble à ceci : (1) le bon langage et sa version exacte ; (2) un gestionnaire de dépendances et les bibliothèques du projet ; (3) une base de données et sa version ; (4) des variables de configuration (clés, ports, URL) ; (5) d'éventuels outils système (un compilateur, une librairie graphique…). Le simple fait que cette liste soit longue et fragile est la justification de Docker : il l'emballe entièrement dans un conteneur.

Exercice 0.2 Intermédiaire · Analyse

Reprenez le tableau des cinq contextes modernes (Cloud, CI/CD, microservices, équipe, IA). Pour chacun, formulez en une phrase le problème concret qu'une équipe rencontrerait sans Docker.

Voir la correction

Cloud : l'application se comporte différemment selon le serveur, obligeant à reconfigurer manuellement chaque machine. CI/CD : le code passe les tests sur la machine de test mais échoue en production car l'environnement diffère. Microservices : deux services exigent des versions incompatibles d'une même bibliothèque et ne peuvent cohabiter sur la même machine. Équipe : chaque nouvel arrivant perd des jours à configurer son poste, et « ça marche chez moi » devient la norme. IA : une mise à jour d'une bibliothèque casse silencieusement le comportement d'un modèle, sans moyen simple de revenir à l'état qui fonctionnait.

Challenge

Repensez à la dernière fois qu'un logiciel a refusé de s'installer ou de démarrer chez vous. Notez le message d'erreur si vous vous en souvenez. Gardez-le de côté : à la fin de la Partie 1, vous devriez être capable d'expliquer pourquoi Docker aurait probablement évité ce problème.

En résumé
  • Une application a besoin de tout un environnement (langage, dépendances, config, OS) pour fonctionner.
  • « Ça marche sur ma machine » survient parce que cet environnement ne voyage pas avec l'application.
  • Docker emballe l'application et son environnement dans un conteneur standardisé, qui tourne à l'identique partout.
  • En 2026, Docker est au cœur du Cloud, du CI/CD, des microservices, du travail en équipe et de l'IA.
  • On apprend d'abord le pourquoi : ensuite, chaque commande devient logique.
1
Partie 1

Comprendre les fondamentaux

Deux chapitres pour poser des bases solides : le problème précis que Docker résout, puis ce qu'est réellement un conteneur — et en quoi il diffère d'une machine virtuelle. Aucune installation ici : on construit d'abord le modèle mental.

Partie 1 · Chapitre 1

Le problème que Docker résout

À la fin de ce chapitre, vous serez capable de…
  • décomposer une application en ses quatre couches : application, dépendances, configuration, système ;
  • expliquer précisément ce qui casse quand on déplace une application d'une machine à une autre ;
  • reconnaître les trois grandes familles de problèmes d'environnement.

Le problème

Au chapitre précédent, nous avons dit qu'une application « ne vit pas seule ». Regardons cela de plus près, car c'est le problème que Docker attaque. Prenons une application web toute simple, écrite en Python. Que faut-il, réellement, pour la faire tourner ?

Le concept : les quatre couches d'une application qui tourne

Sur votre machine, votre application repose sur un empilement de couches. Chacune dépend de celle du dessous :

flowchart TD
  A[" Machine du développeur"] --> B[" Application<br/><span style='font-size:11px'>votre code</span>"]
  B --> C[" Dépendances<br/><span style='font-size:11px'>bibliothèques + versions</span>"]
  C --> D[" Runtime & outils système<br/><span style='font-size:11px'>Python 3.12, libs système…</span>"]
  D --> E[" Système d'exploitation<br/><span style='font-size:11px'>+ configuration, variables</span>"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Une application est le sommet d'une pile. Elle ne fonctionne que si toutes les couches en dessous sont exactement les bonnes.

Le point crucial : votre code ne représente que la couche du haut. Tout le reste — les versions des bibliothèques, le runtime du langage, les outils système, la configuration — s'est installé progressivement sur votre machine. Vous l'avez peut-être oublié, mais votre application en dépend à chaque exécution.

Visualisation : ce qui se passe quand on déplace l'application

Vous envoyez maintenant uniquement votre code (la couche du haut) à un collègue ou sur un serveur. Les couches du dessous, elles, restent sur votre machine. Sur la machine cible, elles sont différentes… ou absentes.

flowchart TB
  subgraph DEV [" Votre machine"]
    direction TB
    d1[Application] --> d2["Dépendances v2.1"] --> d3["Python 3.12"] --> d4[OS + config OK]
  end
  subgraph PROD [" Le serveur"]
    direction TB
    p1[Application] --> p2["Dépendances v1.4 "] --> p3["Python 3.9 "] --> p4[Config manquante ]
  end
  DEV -. "on ne déplace que le code" .-> PROD
  PROD --> X(( Ça casse))
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
  style DEV fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
  style PROD fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
On déplace la couche du haut, mais pas celles du dessous. La moindre différence suffit à tout faire échouer.

Les trois familles de problèmes

Tout ce qui casse lors d'un déplacement se range presque toujours dans l'une de ces trois catégories :

FamilleExemple concretSymptôme typique
VersionsPython 3.12 chez vous, 3.9 sur le serveur ; une bibliothèque en v2 vs v1.Erreur de syntaxe ou fonction « inconnue » qui n'existait pas dans l'ancienne version.
Dépendances manquantesUne bibliothèque installée chez vous, jamais installée ailleurs.ModuleNotFoundError, library not found, l'application ne démarre pas.
ConfigurationUne variable d'environnement, un port, une URL de base de données définie chez vous seulement.Connection refused, valeur nulle, comportement différent sans erreur claire.
La promesse de Docker, reformulée

Docker capture les quatre couches d'un coup — application, dépendances, runtime, configuration — dans une image, puis fait tourner cette image à l'identique sur n'importe quelle machine. Les trois familles de problèmes ci-dessus disparaissent, car il n'y a plus rien qui « reste sur votre machine ».

Un exemple parlant : deux projets, une seule machine

Voici un cas que tout développeur finit par rencontrer. Vous avez deux projets sur votre ordinateur :

  • Projet A a besoin de la version 1 d'une bibliothèque.
  • Projet B a besoin de la version 2 de la même bibliothèque.

Sur une machine « classique », vous ne pouvez installer qu'une seule version à la fois. Installer l'une casse l'autre. Vous voilà coincé. Avec Docker, chaque projet vit dans son propre conteneur, avec sa propre version — isolés, ils s'ignorent totalement et fonctionnent tous les deux. Cette idée d'isolation est si importante qu'elle occupe le chapitre suivant.

Erreur fréquente

« Il suffit d'écrire un fichier README avec la liste des choses à installer. » En théorie, oui. En pratique, ce README est toujours incomplet, vite périmé, et interprété différemment par chacun. Docker remplace le README fragile par une recette exécutable et reproductible : le Dockerfile, que nous découvrirons en Partie 3.

Exercices

Exercice 1.1 Débutant

Classez chacun de ces messages d'erreur dans l'une des trois familles (Versions / Dépendances manquantes / Configuration) :

  1. ModuleNotFoundError: No module named 'requests'
  2. psycopg2.OperationalError: connection refused
  3. SyntaxError apparaissant seulement sur le serveur, jamais chez vous.
Voir la correction

1 → Dépendances manquantes : la bibliothèque requests n'est pas installée sur la machine cible. 2 → Configuration : l'application ne trouve pas sa base de données (mauvaise URL/port/hôte, ou base non démarrée). 3 → Versions : une syntaxe valide dans votre version du langage ne l'est pas dans la version, plus ancienne, du serveur.

Exercice 1.2 Intermédiaire

Reprenez le schéma des quatre couches. Pour une application que vous connaissez, remplissez chaque couche avec des valeurs concrètes (nom du langage et version, deux bibliothèques, un outil système, une variable de configuration). Puis entourez celles qui, selon vous, seraient différentes sur la machine d'un collègue.

Voir la correction

Exemple pour une petite API : Application = votre code ; Dépendances = fastapi 0.115, uvicorn 0.30 ; Runtime = Python 3.12 ; OS/config = variable DATABASE_URL. Les éléments les plus susceptibles de différer sont presque toujours la version du runtime et la configuration — c'est exactement ce que Docker fige.

Challenge

Sans encore connaître la syntaxe, essayez de rédiger en français une « recette » pour reconstruire l'environnement de votre application, étape par étape, comme si vous l'expliquiez à un robot qui part d'une machine vide. Gardez-la : au Chapitre 6, vous la traduirez presque mot pour mot en Dockerfile.

En résumé
  • Une application qui tourne est une pile de quatre couches : application, dépendances, runtime/outils, OS + configuration.
  • Déplacer l'application ne déplace que la couche du haut ; les autres restent sur votre machine.
  • Ce qui casse se range en trois familles : versions, dépendances manquantes, configuration.
  • Docker capture les quatre couches dans une image reproductible, et permet à des environnements incompatibles de cohabiter grâce à l'isolation.
Partie 1 · Chapitre 2

Comprendre les conteneurs

À la fin de ce chapitre, vous serez capable de…
  • définir un conteneur, une image et un runtime, et les distinguer clairement ;
  • expliquer ce que veulent dire « isolation » et « portabilité » ;
  • situer la couche Docker Engine dans la pile logicielle ;
  • expliquer visuellement la différence entre un conteneur et une machine virtuelle.

Le concept : image, conteneur, runtime

Trois mots reviennent sans cesse. Fixons-les une bonne fois avec une analogie simple : la cuisine.

TermeDéfinitionAnalogie « cuisine »
ImageUn modèle figé, en lecture seule, qui contient l'application et tout son environnement.La recette écrite + tous les ingrédients pré-emballés.
ConteneurUne instance en cours d'exécution d'une image. C'est l'application qui tourne, isolée.Le plat effectivement en train de cuire à partir de la recette.
Runtime (Docker Engine)Le moteur qui lit les images et fait tourner les conteneurs sur votre machine.La cuisine et le cuisinier qui exécutent la recette.
La relation à retenir

Une image est un modèle inerte. Quand on la lance, on obtient un conteneur vivant. À partir d'une seule image, on peut lancer autant de conteneurs identiques qu'on veut — comme préparer dix fois le même plat à partir d'une seule recette.

Isolation et portabilité : les deux super-pouvoirs

Deux propriétés font toute la valeur d'un conteneur :

  • Isolation — chaque conteneur croit être seul au monde. Il a ses propres fichiers, ses propres bibliothèques, son propre réseau. Ce qui se passe dans un conteneur ne perturbe ni la machine hôte ni les autres conteneurs. C'est ce qui permet aux deux projets incompatibles du Chapitre 1 de cohabiter.
  • Portabilité — comme l'image contient tout l'environnement, le conteneur se comporte de la même façon partout où Docker est installé : votre portable, celui d'un collègue, un serveur Cloud. Fini le « ça marche sur ma machine ».

Visualisation : la pile logicielle

Où se place Docker sur votre machine ? Empilons les couches, du bas (le matériel) vers le haut (votre application) :

flowchart TD
  A[" Application (dans le conteneur)"] --> B[" Container"]
  B --> C[" Docker Engine<br/><span style='font-size:11px'>le runtime</span>"]
  C --> D[" Système d'exploitation hôte"]
  D --> E[" Matériel (Hardware)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le conteneur s'appuie sur le Docker Engine, qui s'appuie lui-même sur le système d'exploitation de la machine — sans emporter un second OS complet.

Ce dernier point est fondamental et nous mène à la comparaison la plus importante du chapitre.

Conteneur vs Machine virtuelle

Beaucoup confondent les deux. La différence tient à une seule couche, mais elle change tout. Une machine virtuelle (VM) embarque un système d'exploitation invité complet par-dessus un hyperviseur. Un conteneur, lui, partage le système d'exploitation de l'hôte via le Docker Engine.

flowchart TB
  subgraph VM [" Machine virtuelle — lourde"]
    direction TB
    v1[Application] --> v2["Guest OS<br/>(OS complet, plusieurs Go)"]
    v2 --> v3[Hypervisor]
    v3 --> v4[Host OS]
    v4 --> v5[Hardware]
  end
  subgraph CT [" Conteneur — léger"]
    direction TB
    c1[Application] --> c2[Container]
    c2 --> c3[Docker Engine]
    c3 --> c4[Host OS]
    c4 --> c5[Hardware]
  end
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
  style VM fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
  style CT fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
La VM empile un OS invité complet ; le conteneur s'en passe et partage l'OS hôte. C'est toute la différence de poids et de vitesse.
Machine virtuelle Conteneur
Système d'exploitationUn OS invité complet par VMPartage l'OS de l'hôte
PoidsPlusieurs gigaoctetsSouvent quelques mégaoctets à centaines de Mo
DémarrageDizaines de secondes à minutesUne fraction de seconde
DensitéQuelques VM par machineDes dizaines, voire des centaines de conteneurs
IsolationTrès forte (OS séparé)Forte, au niveau du processus
Nuance importante

Conteneurs et VM ne sont pas ennemis : ils sont complémentaires. Dans le Cloud, on fait très souvent tourner des conteneurs à l'intérieur de machines virtuelles. Et sur macOS et Windows, Docker utilise lui-même une légère VM Linux en coulisses — nous y reviendrons à l'installation (Chapitre 3). L'important à ce stade : un conteneur est bien plus léger et rapide qu'une VM parce qu'il n'emporte pas d'OS complet.

Pourquoi cette légèreté compte pour l'IA

Les applications d'IA sont déjà lourdes (grosses bibliothèques, gros modèles). Ajouter par-dessus le poids d'un OS invité complet, comme le ferait une VM, serait coûteux. La légèreté des conteneurs est l'une des raisons pour lesquelles Docker est devenu le standard pour livrer des applications d'IA reproductibles.

Exercices

Exercice 2.1 Débutant

Vrai ou faux, en justifiant en une phrase :

  1. Une image et un conteneur sont la même chose.
  2. On peut lancer plusieurs conteneurs à partir d'une seule image.
  3. Un conteneur embarque un système d'exploitation complet, comme une VM.
Voir la correction

1 → Faux. L'image est le modèle figé (la recette + les ingrédients) ; le conteneur est l'instance en cours d'exécution (le plat qui cuit). 2 → Vrai. Une image sert de moule : on peut en lancer autant de conteneurs identiques qu'on veut. 3 → Faux. Un conteneur partage l'OS de l'hôte via le Docker Engine ; c'est justement ce qui le rend plus léger qu'une VM.

Exercice 2.2 Intermédiaire

Redessinez de mémoire, sur papier, la pile logicielle d'un conteneur (des cinq couches, du matériel à l'application). Puis, à côté, celle d'une VM. Entourez la couche qui, présente dans la VM, est absente dans le conteneur.

Voir la correction

Conteneur : Hardware → Host OS → Docker Engine → Container → Application. VM : Hardware → Host OS → Hypervisor → Guest OS → Application. La couche à entourer est le Guest OS (le système d'exploitation invité complet) : c'est elle que le conteneur évite en partageant l'OS de l'hôte.

Exercice 2.3 Intermédiaire · Pratique

Installez Docker en suivant le Chapitre 3 (cinq minutes), puis revenez ici pour vérifier « en vrai » l'isolation et la légèreté. Lancez deux mini-systèmes Linux différents dans deux conteneurs, et prouvez qu'ils sont isolés :

bash · à exécuter une fois Docker installé
# 1) Entrez dans un Ubuntu conteneurisé et créez-y un fichier
docker run -it --name test-ubuntu ubuntu bash
#   dans le conteneur :
cat /etc/os-release   # → Ubuntu
echo "coucou" > /tmp/preuve.txt
exit

# 2) Lancez un Alpine : autre distribution, autre monde
docker run -it --name test-alpine alpine sh
#   dans le conteneur :
cat /etc/os-release   # → Alpine
ls /tmp/preuve.txt    # → n'existe PAS ici
exit

Question : pourquoi le fichier créé dans Ubuntu est-il introuvable dans Alpine, alors que les deux tournent sur votre même machine ? Et pourquoi ces deux « Linux » démarrent-ils en une fraction de seconde ?

Voir la correction

Chaque conteneur a son propre système de fichiers isolé : le fichier d'Ubuntu n'existe pas dans le monde d'Alpine. C'est l'isolation en action. Et ils démarrent instantanément parce qu'ils ne bootent pas un OS complet (contrairement à une VM) : ils partagent le noyau de votre machine et n'ajoutent qu'une fine couche — la légèreté vue dans ce chapitre. Nettoyez avec docker rm test-ubuntu test-alpine.

Challenge

Expliquez à voix haute, en moins de 60 secondes et sans jargon, la différence entre un conteneur et une VM à quelqu'un qui n'y connaît rien. Si vous y arrivez avec l'analogie de la cuisine ou du conteneur maritime, le concept est acquis. Sinon, relisez la section « Visualisation ».

En résumé
  • Une image est un modèle figé ; un conteneur est une image en cours d'exécution ; le Docker Engine est le moteur qui fait tourner le tout.
  • Les deux super-pouvoirs du conteneur sont l'isolation (chacun croit être seul) et la portabilité (même comportement partout).
  • La pile est : Matériel → OS hôte → Docker Engine → Conteneur → Application.
  • Contrairement à une VM, un conteneur ne transporte pas d'OS invité complet : il partage celui de l'hôte, d'où sa légèreté et sa rapidité.
  • Conteneurs et VM sont complémentaires, pas concurrents.
2
Partie 2

Installation et premiers pas

Fini la théorie : on installe Docker, on vérifie qu'il fonctionne, puis on lance son tout premier conteneur et on apprend les commandes du quotidien. À partir d'ici, gardez un terminal ouvert.

Partie 2 · Chapitre 3

Installer Docker

À la fin de ce chapitre, vous serez capable de…
  • distinguer Docker Desktop de Docker Engine et savoir lequel vous concerne ;
  • installer Docker sur Windows, macOS ou Linux ;
  • vérifier que l'installation fonctionne avec docker --version et docker info ;
  • lancer docker run hello-world et expliquer, étape par étape, ce qui se passe.

Le problème : deux « Docker » qui portent le même nom

Quand on débute, une confusion revient sans cesse : on parle de « Docker », mais il existe en réalité deux produits complémentaires. Clarifions-les tout de suite, cela vous évitera bien des malentendus.

Docker Desktop Docker Engine
Ce que c'estUne application complète avec interface graphique, qui installe et gère tout pour vous.Le cœur de Docker : le moteur (le daemon) et la ligne de commande, sans interface.
Pour quiDéveloppeurs sur leur poste (Windows, macOS, Linux).Serveurs Linux, production, environnements sans interface graphique.
ContientDocker Engine + Compose + interface + outils (scan d'images, gestion visuelle…).Uniquement le moteur et les outils en ligne de commande.
SystèmesWindows, macOS, Linux.Linux (et via Desktop ailleurs).
Comment choisir ?

Si vous apprenez Docker sur votre ordinateur personnel, installez Docker Desktop : c'est le plus simple, tout est inclus. Si vous configurez un serveur Linux (par exemple pour déployer, en Partie 11), vous installerez Docker Engine. Les commandes que vous apprendrez sont identiques dans les deux cas — c'est le même moteur en dessous.

Sur macOS et Windows, Docker s'appuie sur une petite VM Linux

Rappelez-vous le Chapitre 2 : les conteneurs partagent le noyau Linux de l'hôte. Comme Windows et macOS ne sont pas Linux, Docker Desktop fait tourner en coulisses une VM Linux légère (via WSL 2 sur Windows). Vous n'avez rien à gérer : Docker Desktop s'en occupe. Mais cela explique pourquoi il faut installer une application dédiée plutôt qu'un simple exécutable.

Installation selon votre système

La procédure exacte évolue au fil des versions. Téléchargez toujours depuis la source officielledocs.docker.com — et suivez la page correspondant à votre système. Voici la vue d'ensemble.

Windows

  • Prérequis : Windows 10/11 64 bits avec WSL 2 (Windows Subsystem for Linux) activé. Docker Desktop propose de l'activer pendant l'installation.
  • Téléchargez Docker Desktop for Windows, lancez l'installateur, redémarrez si demandé.
  • Ouvrez Docker Desktop : une icône de baleine apparaît dans la barre des tâches quand le moteur est prêt.
  • Vos commandes se tapent dans PowerShell, l'invite de commandes, ou un terminal WSL.

macOS

  • Choisissez la bonne puce : Apple Silicon (M1/M2/M3/M4…) ou Intel. En cas de doute : menu Pomme → « À propos de ce Mac ».
  • Téléchargez Docker Desktop for Mac, glissez l'application dans « Applications », lancez-la.
  • Les commandes se tapent dans l'app Terminal.

Linux

Deux options selon votre besoin :

  • Docker Desktop for Linux (Ubuntu, Debian, Fedora…) si vous voulez l'expérience graphique sur votre poste.
  • Docker Engine si vous êtes sur un serveur ou préférez le tout-en-ligne-de-commande. C'est le choix classique en production.
bash · installation rapide de Docker Engine (script officiel)
# Récupère et exécute le script d'installation officiel de Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Permet d'utiliser docker sans « sudo » (voir l'encadré ci-dessous)
sudo usermod -aG docker $USER
# Puis fermez/rouvrez votre session pour que le changement prenne effet
Le script « get.docker.com » : pratique, mais à connaître

Ce script convient parfaitement pour apprendre et pour des serveurs de test. Pour une vraie production, la documentation officielle recommande souvent l'installation via le gestionnaire de paquets de votre distribution (dépôt apt/dnf officiel de Docker), qui facilite les mises à jour maîtrisées. Nous y reviendrons en Partie 11.

Vérifier que tout fonctionne

Quel que soit votre système, deux commandes confirment que Docker est bien installé et démarré.

bash · vérifier la version installée
docker --version

Vous devriez voir une ligne du type Docker version 27.x.x, build ... (le numéro exact importe peu). Si la commande est « introuvable », Docker n'est pas installé ou pas dans le PATH.

bash · afficher l'état complet du moteur
docker info

docker info interroge le daemon (le moteur qui tourne en arrière-plan). S'il répond avec un long récapitulatif (nombre de conteneurs, d'images, version du noyau…), c'est que le moteur est bien démarré et vous parle. S'il affiche une erreur de connexion, le daemon n'est pas lancé — voir « Si ça ne fonctionne pas » plus bas.

« version » vs « info » : la nuance utile

docker --version vérifie juste que le programme client est installé. docker info vérifie en plus que le moteur tourne et répond. Les deux ensemble = installation réellement opérationnelle.

Votre premier conteneur : hello-world

Le moment est venu. Cette commande est le « bonjour le monde » de Docker. Tapez-la :

bash · lancer le tout premier conteneur
docker run hello-world

Après quelques secondes, Docker affiche un message qui commence par Hello from Docker! et vous félicite. Anodin ? Pas du tout : en une commande, il vient de se passer une chaîne d'événements qui résume tout Docker.

Comprendre ce qui vient de se passer

Décomposons, car ce mécanisme se reproduira à chaque docker run de votre vie de développeur :

flowchart TD
  A["Vous tapez<br/>docker run hello-world"] --> B{"L'image 'hello-world'<br/>est-elle en local ?"}
  B -- Non --> C["Docker la télécharge<br/>depuis Docker Hub"]
  B -- Oui --> D
  C --> D["Docker crée un conteneur<br/>à partir de l'image"]
  D --> E["Le conteneur s'exécute :<br/>il affiche son message"]
  E --> F["Le programme se termine :<br/>le conteneur s'arrête"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le cycle complet d'un « docker run » : chercher l'image, la télécharger si besoin, créer le conteneur, l'exécuter, s'arrêter.
  1. Docker cherche l'image en local. La première fois, vous ne l'avez pas.
  2. Il la télécharge depuis Docker Hub, le registre public d'images (on y reviendra en Partie 3). Vous verrez une ligne Unable to find image... pulling from library/hello-world.
  3. Il crée un conteneur à partir de cette image (l'image = le modèle ; le conteneur = l'instance, souvenez-vous du Chapitre 2).
  4. Il exécute le programme contenu dans l'image : ici, un petit programme qui affiche un texte.
  5. Le programme se termine, donc le conteneur s'arrête. Un conteneur vit tant que son programme principal tourne — retenez bien cela, c'est la cause n°1 de « mon conteneur s'arrête tout seul » que nous verrons au Chapitre 17.
Bonne pratique

Si hello-world fonctionne, votre installation est parfaitement opérationnelle : téléchargement d'image, création et exécution de conteneur, tout le pipeline a été validé. C'est le test de référence à relancer chaque fois que vous doutez de votre environnement Docker.

Si ça ne fonctionne pas

« Cannot connect to the Docker daemon »

Symptôme : une commande répond Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
Cause probable : le moteur Docker n'est pas démarré.
Vérification : sur Windows/macOS, l'icône baleine est-elle présente et stable ? Sur Linux, sudo systemctl status docker.
Solution : lancez Docker Desktop et attendez qu'il soit « running » ; sur Linux, sudo systemctl start docker.

« permission denied » sur Linux

Symptôme : permission denied while trying to connect to the Docker daemon socket.
Cause probable : votre utilisateur n'est pas dans le groupe docker.
Vérification : groups ne mentionne pas docker.
Solution : sudo usermod -aG docker $USER, puis déconnectez-vous et reconnectez-vous (le changement de groupe n'est pris en compte qu'à la nouvelle session).

Challenge

Après un docker run hello-world réussi, tapez docker ps -a. Vous verrez le conteneur hello-world… arrêté. Sauriez-vous expliquer pourquoi il est arrêté et non « en cours d'exécution » ? (Indice : relisez le point 5 ci-dessus. La réponse complète arrive au Chapitre 4.)

En résumé
  • Docker Desktop = tout-en-un avec interface, pour votre poste. Docker Engine = le moteur seul, pour les serveurs Linux. Mêmes commandes.
  • Sur Windows/macOS, Docker fait tourner une VM Linux légère en coulisses (WSL 2 sur Windows).
  • On vérifie l'installation avec docker --version (le client) et docker info (le moteur).
  • docker run hello-world valide toute la chaîne : chercher → télécharger → créer → exécuter → s'arrêter.
  • Un conteneur vit tant que son programme principal tourne, puis s'arrête.
Partie 2 · Chapitre 4

Les premières commandes Docker

À la fin de ce chapitre, vous serez capable de…
  • lancer, lister, arrêter, redémarrer et supprimer des conteneurs ;
  • gérer vos images locales ;
  • lire les logs d'un conteneur, l'inspecter, et entrer dedans avec exec ;
  • vous repérer grâce à un tableau récapitulatif des commandes essentielles.

Le concept : le cycle de vie d'un conteneur

Presque toutes les commandes de ce chapitre agissent sur une même idée : le cycle de vie d'un conteneur. Un conteneur naît (créé et démarré), vit (en cours d'exécution), peut être mis en pause (arrêté), redémarré, puis détruit (supprimé). Gardez ce schéma en tête :

flowchart LR
  A["Image"] -->|docker run| B[" En cours<br/>d'exécution"]
  B -->|docker stop| C[" Arrêté"]
  C -->|docker start| B
  B -->|docker restart| B
  C -->|docker rm| D[" Supprimé"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le cycle de vie : de l'image au conteneur en marche, à l'arrêt, puis à la suppression.

Décortiquons une commande complète

Avant la liste, apprenons à lire une commande docker run. Prenons un serveur web nginx, que nous rendons accessible dans le navigateur :

bash · lancer nginx en arrière-plan, accessible sur le port 8080
docker run -d -p 8080:80 --name mon-nginx nginx

Chaque morceau a un rôle précis :

MorceauRôle
docker runCrée et démarre un nouveau conteneur.
-dDetached : lance le conteneur en arrière-plan et vous rend la main (sinon le terminal reste « occupé » par le conteneur).
-p 8080:80Publish : relie le port 8080 de votre machine au port 80 du conteneur. Format : hôte:conteneur.
--name mon-nginxDonne un nom lisible au conteneur (sinon Docker en invente un aléatoire).
nginxL'image à utiliser. Absente en local ? Docker la télécharge depuis Docker Hub.

Ouvrez maintenant http://localhost:8080 dans votre navigateur : la page d'accueil de nginx s'affiche. Vous venez de faire tourner un vrai serveur web, isolé, en une ligne.

Le sens de -p hôte:conteneur

L'ordre compte et se confond facilement. À gauche, le port sur votre machine (celui que vous tapez dans le navigateur). À droite, le port à l'intérieur du conteneur (celui sur lequel le programme écoute). Ici, nginx écoute sur 80 dans le conteneur ; on l'expose sur 8080 chez nous.

Les commandes essentielles, une par une

Pour chacune : son rôle, un exemple, et l'erreur classique associée.

docker ps — lister les conteneurs actifs

bash
docker ps            # conteneurs EN COURS d'exécution
docker ps -a         # TOUS les conteneurs, y compris arrêtés

Affiche l'identifiant, l'image, le statut, les ports et le nom. Erreur classique : chercher un conteneur arrêté avec docker ps tout court et croire qu'il a disparu — il faut -a.

docker stop / start / restart

bash
docker stop mon-nginx      # arrêt propre (laisse le temps de se fermer)
docker start mon-nginx     # redémarre un conteneur arrêté
docker restart mon-nginx   # stop puis start, en une commande

Erreur classique : stop ne supprime pas le conteneur, il le met seulement en pause. Il apparaît toujours dans docker ps -a et garde ses données.

docker rm / docker rmi — supprimer

bash
docker rm mon-nginx        # supprime un conteneur (il doit être arrêté)
docker rm -f mon-nginx     # force la suppression même s'il tourne
docker rmi nginx           # supprime l'IMAGE nginx
rmrmi

docker rm supprime un conteneur (une instance). docker rmi supprime une image (un modèle) — le i comme image. On ne peut pas supprimer une image encore utilisée par un conteneur existant.

docker images — lister les images locales

bash
docker images     # toutes les images téléchargées ou construites

Affiche le dépôt (repository), le tag (version), l'identifiant et la taille. Utile pour repérer les grosses images à nettoyer.

docker logs — lire la sortie d'un conteneur

bash
docker logs mon-nginx        # affiche les logs
docker logs -f mon-nginx     # suit les logs en temps réel (Ctrl+C pour quitter)

C'est votre outil de débogage n°1. Un conteneur qui « ne marche pas » a presque toujours écrit la raison dans ses logs. On y reviendra longuement au Chapitre 17.

docker inspect — tous les détails

bash
docker inspect mon-nginx     # configuration complète, au format JSON

Renvoie tout : réseau, ports, volumes, variables d'environnement, adresse IP interne. Verbeux, mais précieux quand un détail cloche (nous l'utiliserons pour diagnostiquer les problèmes réseau).

docker exec — entrer dans un conteneur en marche

bash · ouvrir un shell interactif dans le conteneur
docker exec -it mon-nginx bash

Ceci ouvre un terminal à l'intérieur du conteneur, comme si vous vous y connectiez. -i = interactif, -t = terminal. Tapez exit pour ressortir. Indispensable pour explorer ce qui se passe dedans.

Astuce

Certaines images minimales n'ont pas bash. Essayez alors sh : docker exec -it mon-nginx sh. Nous verrons pourquoi (images Alpine) au Chapitre 7.

Tableau récapitulatif

CommandeRôleExemple
docker runCréer + démarrer un conteneurdocker run -d -p 8080:80 nginx
docker psLister les conteneurs actifsdocker ps -a
docker stopArrêter un conteneurdocker stop mon-nginx
docker startRedémarrer un conteneur arrêtédocker start mon-nginx
docker restartArrêter puis redémarrerdocker restart mon-nginx
docker rmSupprimer un conteneurdocker rm -f mon-nginx
docker imagesLister les images localesdocker images
docker rmiSupprimer une imagedocker rmi nginx
docker logsLire la sortie d'un conteneurdocker logs -f mon-nginx
docker inspectDétails complets (JSON)docker inspect mon-nginx
docker execExécuter une commande dedansdocker exec -it mon-nginx sh
Bonne pratique

Prenez l'habitude de nommer vos conteneurs avec --name : c'est bien plus lisible que les identifiants aléatoires, et vos commandes suivantes (stop, logs, rm) deviennent évidentes à écrire.

Si ça ne fonctionne pas

« port is already allocated »

Symptôme : Bind for 0.0.0.0:8080 failed: port is already allocated.
Cause probable : un autre conteneur (ou programme) occupe déjà le port 8080.
Vérification : docker ps pour voir qui utilise le port.
Solution : arrêtez l'autre conteneur, ou choisissez un autre port hôte, par exemple -p 8081:80.

« conflict: unable to remove... container is running »

Symptôme : docker rm refuse de supprimer.
Cause probable : le conteneur tourne encore.
Solution : arrêtez-le d'abord (docker stop), ou forcez avec docker rm -f.

Mini-projet 1 — Votre premier bac à sable

Il est temps de manipuler pour de vrai. Vous allez lancer trois conteneurs très différents et les faire coexister.

Contexte & objectif

Trois briques que vous croiserez dans presque tous vos futurs projets : un serveur web (nginx), un cache mémoire (redis) et une base de données (postgres). L'objectif n'est pas encore de les relier, mais de prendre en main le cycle de vie et les commandes du chapitre.

Étapes

bash · 1) le serveur web nginx
docker run -d -p 8080:80 --name web nginx

Vérifiez sur http://localhost:8080.

bash · 2) le cache redis
docker run -d --name cache redis

Pas de -p ici : nous n'exposons pas redis au navigateur, on le testera de l'intérieur.

bash · 3) la base de données PostgreSQL
docker run -d --name db -e POSTGRES_PASSWORD=secret postgres

PostgreSQL exige un mot de passe : on le fournit via la variable d'environnement -e POSTGRES_PASSWORD=secret (le -e sera au cœur du Chapitre 14).

Tests & résultat attendu

bash · vérifier que les trois tournent
docker ps

Vous devez voir trois conteneurs actifs : web, cache, db. Testons maintenant redis de l'intérieur :

bash · entrer dans redis et le tester
docker exec -it cache redis-cli
# puis, dans l'invite redis :
ping
# → réponse attendue : PONG
# tapez exit pour ressortir

Et jetons un œil aux logs de PostgreSQL, qui indiquent qu'il est prêt à accepter des connexions :

bash
docker logs db # cherchez : « database system is ready to accept connections »

Nettoyage

bash · tout arrêter et supprimer
docker stop web cache db
docker rm web cache db

Erreurs possibles

  • Port 8080 déjà pris → changez pour -p 8081:80.
  • db s'arrête aussitôt → vous avez probablement oublié -e POSTGRES_PASSWORD=... ; regardez docker logs db, le message est explicite.
  • « redis-cli: not found » → vous n'êtes pas dans le conteneur ; refaites docker exec -it cache redis-cli.
Challenge bonus

Relancez les trois conteneurs, puis exposez PostgreSQL sur le port 5432 de votre machine (-p 5432:5432) et redis sur 6379. Avec docker ps, retrouvez la colonne PORTS et expliquez ce qu'elle montre pour chacun. Vous préparez ainsi le terrain des réseaux Docker (Partie 5).

En résumé
  • Un conteneur suit un cycle de vie : run → (stop ↔ start / restart) → rm.
  • -d lance en arrière-plan, -p hôte:conteneur expose un port, --name nomme le conteneur.
  • docker ps -a montre tous les conteneurs ; logs, inspect et exec -it sont vos outils d'observation et de débogage.
  • rm supprime un conteneur, rmi une image.
  • Vous savez lancer et faire coexister plusieurs services indépendants — la base de tout ce qui suit.
3
Partie 3

Images Docker

Jusqu'ici, vous avez utilisé des images fabriquées par d'autres. Vous allez maintenant comprendre comment elles sont faites, puis construire les vôtres avec un fichier appelé Dockerfile. C'est le cœur du métier.

Partie 3 · Chapitre 5

Comprendre les images

À la fin de ce chapitre, vous serez capable de…
  • expliquer ce qu'est une image et pourquoi elle est faite de couches ;
  • distinguer repository, tag et registry ;
  • comprendre le rôle de Docker Hub et lire un nom d'image complet ;
  • suivre le trajet d'une image, du Dockerfile jusqu'au conteneur.

Le concept : une image est un empilement de couches

Au Chapitre 2, nous avons dit qu'une image est un « modèle figé » contenant l'application et son environnement. Regardons de plus près : une image n'est pas un bloc monolithique, c'est un empilement de couches (layers) superposées, chacune en lecture seule.

Chaque couche représente une modification par rapport à la précédente : « partir d'un mini-Linux », puis « installer Python », puis « ajouter mon code », etc. L'image finale est la somme de toutes ces couches empilées.

flowchart TD
  L4["Couche 4 · Votre code applicatif"] --> L3["Couche 3 · Dépendances installées"]
  L3 --> L2["Couche 2 · Runtime (ex. Python)"]
  L2 --> L1["Couche 1 · Système de base (ex. Debian slim)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Une image = des couches empilées, de la base système (en bas) jusqu'à votre code (en haut).
Pourquoi des couches ? Deux avantages énormes

1. Le partage : si dix images utilisent la même couche « Debian slim », Docker ne la stocke et ne la télécharge qu'une seule fois. 2. Le cache : quand vous reconstruisez une image, Docker réutilise les couches inchangées et ne refait que celles qui ont bougé. C'est ce qui rend les reconstructions ultra-rapides — un mécanisme central que nous exploiterons au Chapitre 7.

Le vocabulaire : repository, tag, registry

Trois mots à ne plus jamais confondre. Prenons un nom d'image complet et disséquons-le :

nom d'image complet
docker.io / library / postgres : 16
   │            │          │        │
registry    (espace de    repo      tag
TermeDéfinitionExemple
RegistryLe serveur qui héberge et distribue les images.docker.io (Docker Hub), ou un registry privé.
RepositoryLa collection d'images d'un même logiciel.postgres, nginx, python.
TagUne version précise dans le repository.16, 16.2, alpine, latest.

Quand vous écrivez simplement postgres, Docker complète pour vous : cela signifie docker.io/library/postgres:latest. C'est pour cela que docker run nginx « connaît » nginx : il va le chercher sur Docker Hub.

Le piège du tag latest

latest n'est pas « toujours la dernière version en temps réel » : c'est juste le tag utilisé par défaut quand vous n'en précisez aucun. Il peut pointer vers une version différente d'un jour à l'autre. En projet sérieux, fixez toujours une version explicite (postgres:16) pour des builds reproductibles. Reposer sur latest est une cause classique de « ça marchait la semaine dernière ».

Docker Hub, le registre public

Docker Hub est le registry public par défaut : une immense bibliothèque d'images prêtes à l'emploi. On y trouve des images officielles (maintenues en lien avec les éditeurs : python, node, postgres, nginx…) et des images publiées par la communauté.

Bonne pratique

Privilégiez les images officielles (badge « Official » / « Docker Official Image » sur Docker Hub) ou les images vérifiées d'éditeurs reconnus. Elles sont mieux maintenues, corrigées en cas de faille, et documentées. Nous approfondirons ce réflexe de sécurité au Chapitre 20.

Visualisation : le trajet d'une image

Voici la boucle complète que vous allez apprendre à parcourir dans les deux prochains chapitres, puis à publier vous-même en Partie 11.

flowchart LR
  A[" Dockerfile<br/>(la recette)"] -->|docker build| B[" Image<br/>(couches figées)"]
  B -->|docker push| C[" Registry<br/>(Docker Hub)"]
  C -->|docker pull| D[" Image<br/>en local"]
  D -->|docker run| E[" Conteneur<br/>(en exécution)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Du Dockerfile au conteneur : on construit une image, on la publie, on la récupère ailleurs, on l'exécute.

Voir les couches en pratique

Deux commandes utiles dès maintenant. La première liste vos images locales, la seconde montre l'historique des couches d'une image :

bash
docker images              # vos images locales, avec leur taille
docker history postgres:16 # les couches qui composent l'image, de haut en bas

Exercices

Exercice 5.1 Débutant

Dans le nom node:22-alpine, identifiez le repository et le tag. Que signifie ce tag, à votre avis ?

Voir la correction

Repository = node ; tag = 22-alpine. Le tag indique deux choses : la version majeure de Node (22) et la base système utilisée (Alpine, une distribution Linux ultra-légère — voir Chapitre 7). Le registry est implicite (docker.io/library, soit Docker Hub).

Exercice 5.2 Intermédiaire

Expliquez, avec le mécanisme des couches, pourquoi télécharger python:3.12 puis python:3.12-slim peut être plus rapide pour la seconde qu'on ne l'imaginerait.

Voir la correction

Parce que Docker télécharge et stocke les couches une seule fois et les partage entre images. Si les deux images ont des couches de base communes, Docker les réutilise au lieu de les re-télécharger. Vous ne payez que le coût des couches réellement différentes. (Note : slim et non-slim ne partagent pas forcément toutes leurs couches, mais le principe de réutilisation vaut dès qu'une couche est identique.)

Challenge

Lancez docker history sur deux images que vous avez déjà (par exemple nginx et redis). Repérez la taille de chaque couche. Laquelle pèse le plus lourd ? Gardez cette intuition : réduire la taille des images sera tout l'enjeu du Chapitre 19.

En résumé
  • Une image est un empilement de couches en lecture seule, chacune ajoutant une modification.
  • Les couches sont partagées entre images et mises en cache aux reconstructions — d'où rapidité et économie d'espace.
  • Registry (le serveur) → repository (le logiciel) → tag (la version). docker.io/library/postgres:16.
  • Docker Hub est le registry public par défaut ; préférez les images officielles.
  • Évitez de dépendre de latest : fixez une version pour des builds reproductibles.
Partie 3 · Chapitre 6

Créer sa première image

À la fin de ce chapitre, vous serez capable de…
  • écrire un Dockerfile simple et comprendre chaque instruction ;
  • construire une image avec docker build et la lancer ;
  • connaître le rôle de FROM, WORKDIR, COPY, RUN, EXPOSE, ENV, CMD, ENTRYPOINT ;
  • distinguer CMD et ENTRYPOINT.

Le concept : le Dockerfile est une recette

Souvenez-vous du « README fragile » du Chapitre 1 : la liste de choses à installer, toujours incomplète. Le Dockerfile est sa version exécutable et reproductible. C'est un simple fichier texte qui décrit, étape par étape, comment construire votre image — et Docker suit la recette à la lettre, à l'identique, à chaque fois.

Chaque instruction du Dockerfile crée en général une nouvelle couche (rappelez-vous le Chapitre 5). Écrire un Dockerfile, c'est donc décrire l'empilement de couches de votre image.

Notre première image, extrêmement simple

Commençons par le strict minimum : une petite application Python qui affiche un message. Créez un dossier et deux fichiers.

mon-premier-image/ ├── app.py └── Dockerfile
app.py
print("Bonjour depuis mon premier conteneur Docker ! ")
Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]

Comprendre chaque ligne

InstructionCe qu'elle fait
FROM python:3.12-slimToujours la première ligne. Choisit l'image de base sur laquelle on construit : ici un Python 3.12 léger. On hérite de tout ce qu'elle contient.
WORKDIR /appDéfinit le dossier de travail à l'intérieur de l'image. Les instructions suivantes s'exécutent depuis /app (créé automatiquement).
COPY app.py .Copie un fichier de votre machine vers l'image. Le . signifie « dans le dossier de travail courant » (/app).
CMD ["python", "app.py"]La commande par défaut lancée au démarrage du conteneur. Ici : exécuter notre script.

Construire et lancer

bash · depuis le dossier mon-premier-image/
docker build -t mon-app .

Décryptons : docker build construit une image ; -t mon-app lui donne un nom (tag) ; le . final est le contexte de build — le dossier où Docker cherche le Dockerfile et les fichiers à copier. Ce point est facile à oublier !

bash · exécuter l'image que vous venez de construire
docker run mon-app
# → Bonjour depuis mon premier conteneur Docker ! 

Félicitations : vous n'êtes plus seulement utilisateur d'images, vous en fabriquez. C'est un cap majeur.

Astuce

Après docker build -t mon-app ., retrouvez votre image avec docker images. Elle apparaît aux côtés de python : votre image a réutilisé les couches de python:3.12-slim et ajouté les vôtres par-dessus.

Les instructions qu'il vous reste à connaître

Notre exemple minimal n'utilisait pas tout. Voici les instructions restantes, avec leur usage typique.

RUN — exécuter une commande pendant la construction

RUN lance une commande au moment du build et fige son résultat dans une couche. C'est ainsi qu'on installe des dépendances :

Dockerfile · exemple
RUN pip install requests
RUN vs CMD : la confusion classique

RUN s'exécute pendant la construction de l'image (une fois, pour préparer l'image). CMD s'exécute au démarrage du conteneur (à chaque docker run). On installe avec RUN ; on lance l'application avec CMD.

ENV — définir une variable d'environnement

Dockerfile · exemple
ENV APP_ENV=production

La variable est disponible pendant le build et dans le conteneur au démarrage. Utile pour la configuration — un sujet entier au Chapitre 14 (où l'on verra pourquoi on n'y met jamais de mot de passe).

EXPOSE — documenter le port utilisé

Dockerfile · exemple
EXPOSE 8000
EXPOSE n'ouvre pas le port

C'est une simple documentation : elle indique sur quel port l'application écoute, mais ne publie rien. Pour rendre le port accessible depuis votre machine, il faut toujours -p au docker run (Chapitre 4). EXPOSE et -p sont complémentaires.

ENTRYPOINT vs CMD

Les deux définissent ce qui se lance au démarrage, mais avec une nuance :

  • CMD donne une commande par défaut, facilement remplaçable : docker run mon-app autre-commande écrase le CMD.
  • ENTRYPOINT fixe le programme principal, toujours exécuté ; ce qu'on passe au run devient ses arguments.

On les combine souvent : ENTRYPOINT définit l'outil, CMD ses arguments par défaut.

Dockerfile · combinaison typique
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
En pratique

Pour débuter, un simple CMD suffit dans la grande majorité des cas. Gardez ENTRYPOINT pour quand vous construirez de vrais outils en ligne de commande. Ne vous laissez pas bloquer par cette subtilité.

Si ça ne fonctionne pas

« COPY failed: no such file or directory »

Symptôme : le build échoue sur une ligne COPY.
Cause probable : le fichier n'est pas dans le contexte de build (le dossier passé en .), ou son nom est mal orthographié.
Vérification : lancez-vous bien docker build depuis le dossier qui contient app.py ? Le . final est-il présent ?
Solution : placez-vous dans le bon dossier et vérifiez le nom exact du fichier.

Le conteneur démarre puis s'arrête immédiatement

Symptôme : docker run se termine aussitôt, le conteneur est « Exited ».
Cause probable : c'est normal ici ! Notre script affiche un message puis se termine — donc le conteneur s'arrête (revoir le Chapitre 3, point 5). Un service qui doit rester actif (serveur web) ne se termine pas, lui.
Vérification : docker logs <conteneur> pour voir le message affiché.

Exercices

Exercice 6.1 Débutant

Modifiez app.py pour qu'il affiche votre prénom, puis reconstruisez et relancez l'image. Quelle commande faut-il relancer pour voir le changement ?

Voir la correction

Il faut reconstruire l'image (le code est figé dans une couche au moment du build), puis la relancer : docker build -t mon-app . suivi de docker run mon-app. Un simple docker run sans rebuild réutiliserait l'ancienne image.

Exercice 6.2 Intermédiaire

Ajoutez à app.py l'utilisation de la bibliothèque requests (par exemple afficher le résultat d'une requête). Que devez-vous ajouter au Dockerfile pour que ça fonctionne ?

Voir la correction

Il faut installer la dépendance pendant le build avec RUN, avant le CMD :

FROM python:3.12-slim
WORKDIR /app
RUN pip install requests
COPY app.py .
CMD ["python", "app.py"]

Sans le RUN pip install, le conteneur planterait avec ModuleNotFoundError — exactement la « dépendance manquante » du Chapitre 1, ici résolue proprement. (Au Chapitre 7, on verra qu'on passe en réalité par un fichier requirements.txt.)

Challenge

Transformez l'exemple en un vrai petit serveur web qui reste actif. Indice : utilisez le module intégré de Python python -m http.server 8000 comme CMD, ajoutez EXPOSE 8000, et lancez avec -p 8000:8000. Ouvrez localhost:8000. Vous verrez la différence entre un conteneur qui « se termine » et un service qui « reste en écoute ».

En résumé
  • Le Dockerfile est une recette reproductible ; chaque instruction crée en général une couche.
  • FROM (base) → WORKDIR (dossier) → COPY (fichiers) → RUN (installer, au build) → CMD (lancer, au run).
  • docker build -t nom . construit ; le . est le contexte de build (à ne pas oublier).
  • RUN = pendant la construction ; CMD = au démarrage. EXPOSE documente un port mais ne l'ouvre pas.
  • Pour débuter, un CMD suffit ; ENTRYPOINT viendra pour les outils en ligne de commande.
Partie 3 · Chapitre 7

Dockerfile professionnel

À la fin de ce chapitre, vous serez capable de…
  • ordonner vos instructions pour exploiter le cache et accélérer vos builds ;
  • écrire un fichier .dockerignore ;
  • choisir une image de base adaptée (officielle, slim, alpine) ;
  • appliquer les premières bonnes pratiques de propreté et de sécurité.

Le problème : un build lent et une image obèse

Au chapitre précédent, on a fait « marcher » une image. Ça ne suffit pas en contexte professionnel : on veut des builds rapides et des images légères et sûres. Deux leviers principaux : l'ordre des instructions (pour le cache) et le choix de l'image de base.

Le cache de couches : le comprendre pour l'exploiter

Quand Docker reconstruit une image, il parcourt le Dockerfile de haut en bas. Pour chaque instruction, il se demande : « rien n'a changé ici depuis la dernière fois ? » Si oui, il réutilise la couche en cache au lieu de la refaire. Mais dès qu'une instruction change, toutes les couches suivantes sont invalidées et refaites.

La règle d'or de l'ordre

Placez ce qui change rarement en haut, et ce qui change souvent en bas. Vos dépendances changent rarement ; votre code change à chaque instant. Donc : installez les dépendances avant de copier le code.

Mauvaise approche

Dockerfile · le code est copié AVANT l'installation
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

Ici, COPY . . copie tout le projet avant l'installation. Résultat : à la moindre modification d'une ligne de code, Docker invalide la copie et refait pip install en entier — même si les dépendances n'ont pas bougé. Chaque build réinstalle tout. Lent.

Bonne approche

Dockerfile · on copie d'abord les dépendances, puis le code
FROM python:3.12-slim
WORKDIR /app

# 1) On copie SEULEMENT le fichier de dépendances…
COPY requirements.txt .
# 2) …et on installe. Cette couche est mise en cache.
RUN pip install --no-cache-dir -r requirements.txt

# 3) Le code, qui change souvent, vient EN DERNIER.
COPY . .
CMD ["python", "app.py"]

Maintenant, tant que requirements.txt ne change pas, Docker réutilise la couche d'installation en cache et ne refait que la copie du code. Les builds passent de plusieurs minutes à quelques secondes.

flowchart TD
  A["Modification d'une ligne de code"] --> B{"Où est copié le code<br/>dans le Dockerfile ?"}
  B -->|"COPY . . AVANT le RUN"| C[" pip install refait<br/>à chaque build"]
  B -->|"COPY . . APRÈS le RUN"| D[" pip install réutilisé<br/>depuis le cache"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Un simple changement d'ordre décide si vos dépendances sont réinstallées ou réutilisées.

Le fichier .dockerignore

Le COPY . . copie tout le dossier dans l'image — y compris des fichiers inutiles, lourds, voire sensibles. Le fichier .dockerignore (à placer à la racine, comme un .gitignore) liste ce qu'il faut exclure.

.dockerignore · exemple
# Dépendances déjà réinstallées dans l'image
node_modules
__pycache__
# Historique et fichiers de dev
.git
.gitignore
# Secrets — ne JAMAIS les embarquer dans une image
.env
*.key
# Divers
*.log
Dockerfile
README.md
Bonne pratique — triple bénéfice

Un bon .dockerignore rend l'image plus légère, le build plus rapide (moins de contexte envoyé au moteur) et plus sûr (pas de .env ni de clés copiés par accident). C'est l'un des réflexes qui distingue un Dockerfile amateur d'un Dockerfile pro.

Choisir son image de base : officielle, slim, alpine

La ligne FROM décide à elle seule d'une bonne partie de la taille et de la sécurité de votre image. Trois grandes familles :

TypeExempleTailleQuand l'utiliser
Complète / officiellepython:3.12Lourde (souvent > 1 Go)En cas de doute, ou si vous avez besoin de nombreux outils système. Confortable mais grosse.
Slimpython:3.12-slimBien plus légèreLe bon défaut pour la plupart des applications : basée sur Debian, allégée du superflu.
Alpinepython:3.12-alpineMinimale (quelques Mo)Quand la taille est critique. Basée sur Alpine Linux ; attention, certains paquets système s'y installent différemment.
Alpine n'est pas toujours le bon choix

Alpine est séduisant par sa taille, mais il utilise une bibliothèque C différente (musl au lieu de glibc). Résultat : certaines bibliothèques — notamment en IA/data science (compilations natives, roues précompilées) — s'installent mal ou lentement sur Alpine. Pour ces cas, slim est souvent le meilleur compromis taille/compatibilité. Nous le reverrons dans la partie IA.

Règle simple

Débutez avec -slim. N'allez vers alpine que si vous mesurez un vrai besoin de réduction de taille et que vos dépendances le supportent. « Mesurer d'abord, optimiser ensuite » — tout le Chapitre 19.

Première bonne pratique de sécurité : ne pas tourner en root

Par défaut, un conteneur s'exécute en tant que root (super-utilisateur). Si une faille de l'application est exploitée, l'attaquant hérite de ces droits élevés. La parade : créer un utilisateur sans privilèges et basculer dessus avec USER.

Dockerfile · exécuter en utilisateur non-privilégié
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

# Créer un utilisateur non-root et lui donner la main
RUN useradd --create-home appuser
USER appuser

CMD ["python", "app.py"]
On approfondira

La sécurité Docker a son chapitre dédié (Chapitre 20) : images de confiance, mises à jour, secrets, scan, moindre privilège. Ici, retenez juste le premier réflexe : ne pas rester root sans raison.

Récapitulatif : l'anatomie d'un Dockerfile pro

Dockerfile · le modèle à retenir
FROM python:3.12-slim          # base légère et fixée
WORKDIR /app                    # dossier de travail
COPY requirements.txt .         # dépendances d'abord (cache)
RUN pip install --no-cache-dir -r requirements.txt
COPY . .                        # code ensuite (change souvent)
RUN useradd --create-home appuser
USER appuser                    # non-root
EXPOSE 8000                     # documentation du port
CMD ["python", "app.py"]     # commande de démarrage

Exercices

Exercice 7.1 Débutant

On vous donne ce Dockerfile. Quel problème de performance a-t-il, et comment le corriger ?

FROM node:22-slim
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
Voir la correction

Le code est copié avant l'installation, donc npm install est refait à chaque changement de code. On copie d'abord les manifestes de dépendances :

FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]

Ainsi, tant que package.json ne change pas, npm install est servi depuis le cache.

Exercice 7.2 Intermédiaire

Écrivez le .dockerignore d'un projet Node.js contenant un dossier node_modules, un fichier .env avec des secrets, un dossier .git et des logs. Justifiez chaque exclusion.

Voir la correction
node_modules   # réinstallé par npm install dans l'image
.env           # secrets : jamais dans une image
.git           # historique inutile et lourd
*.log          # logs de dev sans valeur

Bénéfice : image plus légère, build plus rapide, et surtout aucun secret embarqué par accident.

Challenge Pro

Reprenez l'image de votre Mini-projet ou du Chapitre 6. Comparez sa taille selon la base : construisez-la une fois avec python:3.12, une fois avec python:3.12-slim. Notez l'écart avec docker images. Puis réfléchissez : dans quel cas l'écart de taille justifie-t-il le risque d'incompatibilité d'Alpine ?

En résumé
  • Docker met les couches en cache ; une instruction modifiée invalide toutes les suivantes.
  • Règle d'or : dépendances d'abord, code ensuite — copiez requirements.txt/package.json et installez avant le COPY . ..
  • .dockerignore allège l'image, accélère le build et évite d'embarquer des secrets.
  • Choisissez la base avec soin : slim est le bon défaut ; alpine pour la taille extrême, avec prudence (surtout en IA).
  • Premier réflexe sécurité : un utilisateur non-root avec USER.
4
Partie 4

Conteneuriser de vraies applications

La partie la plus pratique jusqu'ici. Vous allez conteneuriser quatre applications réelles — Java, Node.js, Python et un frontend — et découvrir que, sous la variété des langages, la démarche Docker reste la même.

Partie 4 · Chapitre 8

Docker + Java (Spring Boot) + PostgreSQL

À la fin de ce chapitre, vous serez capable de…
  • conteneuriser une application Java Spring Boot (fichier .jar) ;
  • configurer sa connexion à une base via des variables d'environnement ;
  • faire communiquer l'application et PostgreSQL, chacun dans son conteneur ;
  • comprendre pourquoi l'hôte de la base n'est pas localhost mais le nom du conteneur.

Le contexte

Notre application est une petite API REST de gestion de tâches, construite avec Spring Boot, qui stocke ses données dans PostgreSQL. Peu importe que vous maîtrisiez Spring : ce qui nous intéresse, c'est la conteneurisation. L'architecture visée :

flowchart LR
  U["Client / navigateur"] -->|HTTP 8080| A[" Conteneur<br/>Spring Boot (API)"]
  A -->|JDBC 5432| D[" Conteneur<br/>PostgreSQL"]
  D --> V[" Volume<br/>(données persistantes)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Deux conteneurs qui communiquent : l'API Spring Boot et la base PostgreSQL.

Le projet

Une application Spring Boot se compile en un unique fichier .jar exécutable. On suppose que vous l'avez généré avec le wrapper Maven fourni par Spring :

bash · construire le .jar (sur votre machine)
./mvnw clean package -DskipTests
# produit par exemple : target/tasks-api-0.0.1-SNAPSHOT.jar
tasks-api/ ├── src/ … ├── target/ │ └── tasks-api-0.0.1-SNAPSHOT.jar ├── .dockerignore └── Dockerfile

Le point clé côté Spring : la configuration de la base doit être lisible depuis l'environnement, pas codée en dur. Dans application.properties, on utilise des variables :

src/main/resources/application.properties
spring.datasource.url=${SPRING_DATASOURCE_URL}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
spring.jpa.hibernate.ddl-auto=update

Le Dockerfile

Notre .jar est déjà construit : l'image n'a plus qu'à fournir un Java d'exécution (JRE) et lancer le jar.

Dockerfile
# Image officielle avec juste le nécessaire pour EXÉCUTER du Java 21
FROM eclipse-temurin:21-jre
WORKDIR /app

# On copie le jar construit et on le renomme app.jar
COPY target/*.jar app.jar

# Utilisateur non-root (bonne pratique du Chapitre 7)
RUN useradd --create-home appuser
USER appuser

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Il y a encore mieux : le multi-stage

Ici, on construit le jar hors de Docker, puis on le copie. Il existe une approche plus propre où Docker construit lui-même le jar dans une première étape, puis n'en garde que le résultat — le multi-stage build. On le découvre au Chapitre 11 (frontend) et on l'approfondit au Chapitre 18.

bash · construire l'image
docker build -t tasks-api .

Faire communiquer l'API et PostgreSQL

Deux conteneurs qui doivent se parler ont besoin d'être sur le même réseau Docker. Les réseaux ont leur chapitre dédié (Chapitre 12) ; pour l'instant, retenez juste le geste : on crée un réseau, et on y attache les deux conteneurs.

bash · 1) créer un réseau dédié
docker network create tasks-net
bash · 2) lancer PostgreSQL sur ce réseau
docker run -d --name db --network tasks-net \
  -e POSTGRES_DB=tasks \
  -e POSTGRES_USER=tasks \
  -e POSTGRES_PASSWORD=secret \
  postgres:16
bash · 3) lancer l'API, connectée à la base
docker run -d --name api --network tasks-net -p 8080:8080 \
  -e SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/tasks \
  -e SPRING_DATASOURCE_USERNAME=tasks \
  -e SPRING_DATASOURCE_PASSWORD=secret \
  tasks-api
Le détail qui bloque tout le monde : db et non localhost

Regardez l'URL : jdbc:postgresql://db:5432/tasks. L'hôte est db — le nom du conteneur PostgreSQL. Sur un même réseau Docker, chaque conteneur est joignable par son nom. Écrire localhost ici échouerait : pour l'API, « localhost » désigne son propre conteneur, où il n'y a pas de base. Ce point est si important qu'on lui consacre une section entière au Chapitre 12.

Vérifier

bash
docker logs -f api   # attendez « Started ...Application » et « HikariPool ... »
# puis testez l'API, par ex. :
curl http://localhost:8080/tasks

Si ça ne fonctionne pas

« Connection refused » ou « UnknownHostException: db »

Symptôme : l'API n'atteint pas la base.
Causes probables : les deux conteneurs ne sont pas sur le même réseau ; ou l'URL utilise localhost ; ou la base n'était pas encore prête au démarrage de l'API.
Vérification : docker network inspect tasks-net montre-t-il les deux conteneurs ? L'URL pointe-t-elle bien sur db ?
Solution : attachez les deux au réseau, utilisez le nom db, et relancez l'API après la base. (Le démarrage ordonné sera résolu proprement par depends_on + healthcheck au Chapitre 15.)

Challenge

Trois conteneurs, une commande à corriger : vous lancez l'API mais oubliez --network tasks-net. Que se passe-t-il, et quel message trouvez-vous dans docker logs api ? Corrigez, puis expliquez avec vos mots pourquoi l'isolation réseau protège… tout en imposant de déclarer explicitement qui parle à qui.

En résumé
  • Une app Spring Boot se conteneurise en copiant son .jar dans une image JRE et en le lançant.
  • La configuration de la base passe par des variables d'environnement, jamais en dur dans le code ou l'image.
  • Deux conteneurs communiquent s'ils partagent un réseau Docker ; ils s'adressent par leur nom de conteneur.
  • L'hôte de la base est db (le nom du conteneur), pas localhost.
Partie 4 · Chapitre 9

Docker + Node.js

À la fin de ce chapitre, vous serez capable de…
  • conteneuriser une petite API Node.js (Express) de bout en bout ;
  • gérer proprement package.json, npm install et le cache ;
  • lire un port et une configuration depuis l'environnement ;
  • suivre les logs de votre application conteneurisée.

Le contexte

Une mini-API qui répond en JSON. Deux fichiers suffisent. On applique directement les bonnes pratiques du Chapitre 7.

node-api/ ├── package.json ├── server.js ├── .dockerignore └── Dockerfile
package.json
{
  "name": "node-api",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": { "start": "node server.js" },
  "dependencies": { "express": "^4.19.2" }
}
server.js
const express = require("express");
const app = express();

// Le port vient de l'environnement, avec une valeur par défaut
const PORT = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.json({ message: "Bonjour depuis Node.js dans Docker " });
});

app.listen(PORT, () => {
  console.log(`API à l'écoute sur le port ${PORT}`);
});

Le Dockerfile

Dockerfile
FROM node:22-slim
WORKDIR /app

# 1) Dépendances d'abord (cache) — on copie les manifestes seuls
COPY package.json package-lock.json* ./
RUN npm install --omit=dev

# 2) Puis le code applicatif
COPY . .

ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]
.dockerignore
node_modules
npm-debug.log
.env
.git
Pourquoi exclure node_modules ?

Vos node_modules locaux peuvent contenir des binaires compilés pour votre système, incompatibles avec l'image Linux. En les excluant et en laissant npm install les régénérer dans l'image, on garantit des dépendances propres et adaptées. C'est aussi ce qui rend le cache du Chapitre 7 efficace.

Construire, lancer, observer

bash
docker build -t node-api .
docker run -d -p 3000:3000 --name api node-api
curl http://localhost:3000
# → {"message":"Bonjour depuis Node.js dans Docker "}
bash · lire les logs (votre console.log s'y trouve)
docker logs -f api
# → API à l'écoute sur le port 3000
Changer le port sans reconstruire

Le port est une variable d'environnement. Vous pouvez le changer au lancement sans rebuild : docker run -e PORT=4000 -p 4000:4000 node-api. La souplesse de la configuration par l'environnement, en action.

Si ça ne fonctionne pas

curl ne répond pas alors que le conteneur tourne

Cause fréquente : incohérence entre le port de l'app, l'EXPOSE et le -p. Si l'app écoute sur 3000 mais que vous faites -p 3000:4000, rien ne répond.
Vérification : le port de droite dans -p doit être exactement celui sur lequel l'app écoute (ici 3000).
Solution : alignez app, EXPOSE et la partie droite de -p.

Challenge

Ajoutez une route /health qui renvoie { "status": "ok" }. Reconstruisez, relancez, et testez-la. Question : combien de couches Docker a-t-il refait au rebuild, et pourquoi npm install n'a-t-il (normalement) pas été relancé ?

En résumé
  • On copie package.json et on installe avant le code, pour profiter du cache.
  • Le port et la config se lisent dans process.env, avec des valeurs par défaut.
  • node_modules est exclu via .dockerignore et régénéré dans l'image.
  • docker logs -f affiche vos console.log : votre premier réflexe de débogage.
Partie 4 · Chapitre 10

Docker + Python (FastAPI)

À la fin de ce chapitre, vous serez capable de…
  • conteneuriser une API Python moderne avec FastAPI et Uvicorn ;
  • gérer les dépendances via requirements.txt ;
  • lancer un serveur web qui reste actif dans le conteneur ;
  • réutiliser un patron que l'on retrouvera tel quel dans la partie IA.

Le contexte

FastAPI est aujourd'hui l'un des frameworks web Python les plus utilisés — et, bonne nouvelle, c'est exactement celui qui nous servira pour les API d'IA (Partie 13). Le patron de ce chapitre vous resservira directement.

python-api/ ├── app/ │ └── main.py ├── requirements.txt ├── .dockerignore └── Dockerfile
requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
app/main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def racine():
    return {"message": "Bonjour depuis FastAPI dans Docker "}

@app.get("/sante")
def sante():
    return {"status": "ok"}

Le Dockerfile

Dockerfile
FROM python:3.12-slim
WORKDIR /app

# Dépendances d'abord (cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Puis le code
COPY ./app ./app

EXPOSE 8000
# --host 0.0.0.0 est INDISPENSABLE dans un conteneur (voir encadré)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
L'erreur n°1 des API Python en conteneur : --host 0.0.0.0

Par défaut, Uvicorn (comme Flask, etc.) n'écoute que sur 127.0.0.1 — c'est-à-dire l'intérieur du conteneur uniquement. Vos requêtes depuis l'hôte n'atteignent jamais l'application, et curl renvoie « connexion refusée » alors que le conteneur tourne. La solution est d'écouter sur 0.0.0.0 (toutes les interfaces). Retenez-le : c'est la cause de la moitié des « ça tourne mais je n'y accède pas ».

Construire, lancer, tester

bash
docker build -t python-api .
docker run -d -p 8000:8000 --name api python-api

curl http://localhost:8000
# → {"message":"Bonjour depuis FastAPI dans Docker "}
curl http://localhost:8000/sante
# → {"status":"ok"}
La documentation interactive, offerte

FastAPI génère automatiquement une documentation interactive. Ouvrez http://localhost:8000/docs dans votre navigateur : vous pouvez tester vos routes à la souris. Pratique pour vérifier qu'une API conteneurisée répond bien.

Si ça ne fonctionne pas

« Connection reset » / pas de réponse, conteneur pourtant actif

Cause quasi certaine : vous avez oublié --host 0.0.0.0 dans la commande Uvicorn.
Vérification : docker logs api affiche-t-il Uvicorn running on http://127.0.0.1:8000 ? Le 127.0.0.1 est le signe du problème.
Solution : ajoutez --host 0.0.0.0 au CMD, reconstruisez, relancez.

Challenge

Ajoutez une route POST /echo qui reçoit un JSON { "texte": "..." } et le renvoie en majuscules. Testez-la avec curl -X POST -H "Content-Type: application/json" -d '{"texte":"docker"}' http://localhost:8000/echo. Ce petit exercice pose exactement les fondations d'une API d'IA — vous y reviendrez en Partie 13.

En résumé
  • FastAPI + Uvicorn se conteneurise comme toute app Python : requirements.txt d'abord, code ensuite.
  • Dans un conteneur, un serveur web doit écouter sur 0.0.0.0, jamais seulement sur 127.0.0.1.
  • Ce patron FastAPI est le même que celui des API d'IA à venir.
Partie 4 · Chapitre 11

Docker + frontend

À la fin de ce chapitre, vous serez capable de…
  • comprendre la différence entre construire et servir un frontend ;
  • écrire votre premier multi-stage build ;
  • servir des fichiers statiques avec Nginx dans un conteneur minuscule ;
  • expliquer pourquoi l'image finale ne contient pas Node.js.

Le problème : un frontend, ce sont deux moments très différents

Une application frontend moderne (React, Vue, Angular, Svelte…) vit en deux temps :

  • La construction (build). Sur du code source, on lance un outil (Node.js + un bundler) qui produit des fichiers statiques optimisés : du HTML, du CSS et du JavaScript. Ce moment a besoin de Node et de toutes les dépendances de développement.
  • Le service (runtime). Une fois construits, ces fichiers statiques n'ont plus besoin de Node : un simple serveur web comme Nginx suffit à les livrer au navigateur.
flowchart LR
  A[" Code source<br/>(React/Vue…)"] -->|npm run build| B[" Fichiers statiques<br/>HTML · CSS · JS"]
  B --> C[" Nginx<br/>sert les fichiers"]
  C --> D[" Conteneur léger"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
On construit avec Node, mais on ne sert qu'avec Nginx. Deux besoins, deux étapes.
La mauvaise approche : tout mettre dans une seule image

Si on met Node et le serveur dans la même image, on embarque des centaines de mégaoctets d'outils de build totalement inutiles en production : image obèse, surface d'attaque plus grande, démarrage plus lent. Il y a bien mieux.

La solution : le multi-stage build

Un Dockerfile peut contenir plusieurs étapes (FROM successifs). L'idée géniale : on construit dans une première étape riche (avec Node), puis on ne copie que le résultat dans une seconde étape minimale (Nginx). Tout l'outillage de la première étape est jeté.

frontend/ ├── src/ … ├── package.json ├── nginx.conf ├── .dockerignore └── Dockerfile
Dockerfile · multi-stage (build avec Node, service avec Nginx)
# ---------- ÉTAPE 1 : build ----------
FROM node:22-slim AS build
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm install
COPY . .
RUN npm run build          # produit /app/dist (fichiers statiques)

# ---------- ÉTAPE 2 : runtime ----------
FROM nginx:alpine
# On copie UNIQUEMENT les fichiers construits depuis l'étape « build »
COPY --from=build /app/dist /usr/share/nginx/html
# Configuration Nginx adaptée aux applications monopage (SPA)
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
La ligne magique : COPY --from=build

--from=build va chercher un fichier dans l'étape nommée build, sans rien emporter d'autre. L'image finale est basée sur nginx:alpine (quelques Mo) et ne contient ni Node, ni les node_modules, ni le code source — seulement les fichiers statiques. C'est tout l'intérêt du multi-stage, approfondi au Chapitre 18.

nginx.conf · router les routes vers index.html (SPA)
server {
  listen 80;
  location / {
    root /usr/share/nginx/html;
    index index.html;
    # Renvoie index.html pour toute route inconnue (routing côté client)
    try_files $uri $uri/ /index.html;
  }
}

Construire et servir

bash
docker build -t frontend .
docker run -d -p 8080:80 --name web frontend
# Ouvrez http://localhost:8080 : votre application s'affiche,
# servie par un conteneur qui ne contient même pas Node.
Comparez les tailles

Faites docker images et observez : l'image finale à base de nginx:alpine pèse une fraction de ce qu'aurait pesé une image Node complète avec les node_modules. Vous venez de faire, sans le savoir, de l'optimisation d'image — le sujet du Chapitre 19.

Si ça ne fonctionne pas

Page blanche ou 404 en rechargeant une sous-page

Symptôme : l'accueil marche, mais recharger /profil renvoie une 404.
Cause probable : Nginx cherche un fichier /profil qui n'existe pas — le routing est géré côté JavaScript.
Solution : la directive try_files ... /index.html; ci-dessus renvoie index.html pour toutes les routes, laissant l'app gérer la navigation.

« COPY --from=build ... not found »

Cause probable : le dossier de sortie du build n'est pas /app/dist (certains outils produisent build/ au lieu de dist/).
Vérification : quel dossier votre npm run build génère-t-il réellement ?
Solution : ajustez le chemin source du COPY --from=build en conséquence.

Challenge Pro

Reprenez ce frontend et le backend Node.js du Chapitre 9. Sans encore utiliser Compose, faites-les cohabiter : lancez les deux conteneurs sur un même réseau (docker network create), et configurez le frontend pour appeler l'API par son nom de conteneur. Vous assemblez déjà, à la main, ce que Docker Compose automatisera dès la Partie 8.

En résumé
  • Un frontend se construit (avec Node) puis se sert (avec Nginx) : deux besoins distincts.
  • Le multi-stage build construit dans une étape riche et ne copie que le résultat dans une étape minimale.
  • COPY --from=build récupère les fichiers d'une étape précédente ; l'image finale ne contient ni Node ni sources.
  • Pour une SPA, try_files ... /index.html; évite les 404 sur les routes gérées côté client.
5
Partie 5

Réseaux Docker

Vous avez déjà fait communiquer deux conteneurs au Chapitre 8. Il est temps de comprendre comment, pour ne plus jamais buter sur un « connection refused ».

Partie 5 · Chapitre 12

Comprendre les réseaux

À la fin de ce chapitre, vous serez capable de…
  • expliquer comment des conteneurs communiquent entre eux ;
  • utiliser le nom d'un conteneur comme adresse (hostname) ;
  • distinguer « port interne » et « port publié » ;
  • expliquer une bonne fois pourquoi c'est postgres:5432 et non localhost:5432.

Le problème

Un conteneur est isolé (Chapitre 2) : par défaut, il ne voit pas les autres. Or une application réelle est faite de plusieurs services qui doivent se parler : un backend interroge une base, un frontend appelle le backend. Comment percer l'isolation, juste ce qu'il faut, entre les bons conteneurs ?

Le concept : un réseau Docker est un « couloir privé »

Un réseau Docker relie des conteneurs entre eux. Deux conteneurs sur le même réseau peuvent communiquer ; deux conteneurs sur des réseaux différents s'ignorent. C'est comme un couloir privé : seuls ceux qui y ont une porte peuvent se rendre visite.

Docker fournit un service génial sur ces réseaux : le DNS interne. Chaque conteneur y est joignable par son nom. Pas besoin de connaître son adresse IP (qui change à chaque redémarrage) : on l'appelle par son nom de conteneur, point.

flowchart TB
  subgraph NET [" Réseau Docker « app-net »"]
    direction LR
    F["Frontend"] -->|"appelle backend:8000"| B["Backend"]
    B -->|"appelle postgres:5432"| P["PostgreSQL"]
  end
  U["Navigateur"] -->|"localhost:8080 (port publié)"| F
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
  style NET fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
Sur un même réseau, les conteneurs s'appellent par leur nom. Seul le frontend publie un port vers l'extérieur.

Créer et utiliser un réseau

bash
docker network create app-net          # créer un réseau
docker run -d --name db --network app-net postgres:16
docker run -d --name api --network app-net mon-api
docker network inspect app-net         # voir qui est connecté

Une fois db et api sur app-net, l'API atteint la base à l'adresse db:5432. Le nom db est résolu automatiquement par le DNS interne de Docker.

Le cœur du chapitre : postgres:5432 vs localhost:5432

C'est la confusion à éliminer. Le mot localhost signifie « moi-même ». Le problème : à l'intérieur d'un conteneur, « moi-même » désigne ce conteneur précis, pas votre machine ni les autres conteneurs.

localhost:5432 depuis l'API

L'API cherche une base dans son propre conteneur. Il n'y en a pas → connection refused.

db:5432 depuis l'API

Le DNS Docker résout db vers le conteneur PostgreSQL sur le réseau. La connexion aboutit.

La règle à graver

Entre conteneurs, on s'adresse par le nom du conteneur. localhost ne désigne l'autre conteneur dans aucune situation. Le seul endroit où vous utilisez localhost, c'est depuis votre machine vers un port publié avec -p.

Port interne vs port publié

Deux notions distinctes qu'on confond souvent :

  • Port interne : le port sur lequel un service écoute dans le réseau Docker. Les autres conteneurs l'utilisent (ex. db:5432). Aucun -p n'est nécessaire pour cette communication interne.
  • Port publié (-p hôte:conteneur) : uniquement pour exposer un service à votre machine (le navigateur). Sans -p, le service reste joignable par les autres conteneurs mais invisible depuis l'hôte.
Conséquence pratique

Votre base de données n'a pas besoin d'un -p 5432:5432 si seuls d'autres conteneurs l'utilisent. Ne pas la publier est même plus sûr : elle reste inaccessible depuis l'extérieur. On ne publie que ce qui doit l'être.

Si ça ne fonctionne pas

« could not translate host name » / « connection refused »

Vérifiez dans l'ordre : (1) les deux conteneurs sont-ils sur le même réseau ? (docker network inspect app-net) ; (2) utilisez-vous le nom du conteneur et non localhost ? ; (3) le service cible écoute-t-il bien sur le port indiqué et sur 0.0.0.0 (Chapitre 10) ? ; (4) était-il prêt au moment de la connexion ?

Challenge

Lancez deux conteneurs sur app-net, entrez dans le premier (docker exec -it ... sh) et « pinguez » le second par son nom (ping autre-conteneur). Puis créez un second réseau, déplacez-y un conteneur, et constatez que le ping échoue. Vous venez de vérifier l'isolation réseau de vos propres mains.

En résumé
  • Un réseau Docker relie des conteneurs ; ceux qui n'y sont pas s'ignorent.
  • Le DNS interne permet de joindre un conteneur par son nom — pas besoin de son IP.
  • Entre conteneurs : nom-du-conteneur:port. Jamais localhost.
  • -p ne sert qu'à publier un service vers votre machine ; la communication interne n'en a pas besoin.
6
Partie 6

Stockage & persistance

Question piège : si vous supprimez un conteneur PostgreSQL, que deviennent vos données ? La réponse — et sa solution, les volumes — est indispensable avant de bâtir quoi que ce soit de sérieux.

Partie 6 · Chapitre 13

Volumes Docker

À la fin de ce chapitre, vous serez capable de…
  • expliquer pourquoi les données d'un conteneur disparaissent à sa suppression ;
  • utiliser un volume pour rendre des données persistantes ;
  • distinguer un volume d'un bind mount et savoir quand utiliser chacun ;
  • faire survivre les données d'une base PostgreSQL à la destruction de son conteneur.

Le problème : un conteneur est éphémère

Imaginez : vous lancez PostgreSQL, créez des tables, insérez des données précieuses. Puis vous supprimez le conteneur (docker rm) pour le recréer avec une nouvelle option. Vous relancez… et tout a disparu. Base vide. Panique.

Ce n'est pas un bug, c'est le fonctionnement normal. Le système de fichiers d'un conteneur est éphémère : il naît et meurt avec le conteneur. Tout ce qui y est écrit est perdu à sa suppression. Parfait pour une application sans état — catastrophique pour une base de données.

Nuance : arrêter n'est pas supprimer

docker stop puis docker start conserve les données (le conteneur existe toujours). C'est docker rm — la suppression — qui les efface. Mais dépendre de « ne jamais supprimer le conteneur » n'est pas une stratégie viable. Il faut découpler les données du conteneur.

Le concept : le volume, un coffre-fort hors du conteneur

Un volume est un espace de stockage géré par Docker, qui vit en dehors du cycle de vie des conteneurs. On le « branche » sur un dossier du conteneur : tout ce que le conteneur écrit dans ce dossier va en réalité dans le volume, qui survit à la suppression du conteneur.

flowchart TD
  C[" Conteneur PostgreSQL<br/>écrit dans /var/lib/postgresql/data"] --> V[" Volume Docker<br/>pgdata"]
  V --> H[" Stockage de la machine hôte<br/>(géré par Docker)"]
  X[" On supprime le conteneur"] -.->|"les données restent"| V
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le conteneur écrit dans un dossier relié à un volume. Le conteneur peut disparaître : le volume, et donc les données, restent.

Mise en pratique avec PostgreSQL

PostgreSQL range ses données dans /var/lib/postgresql/data. On y branche un volume nommé pgdata :

bash · lancer PostgreSQL avec un volume persistant
docker run -d --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

Le format de -v est nom_du_volume:chemin_dans_le_conteneur. Docker crée le volume pgdata automatiquement s'il n'existe pas. Testez la persistance :

bash · la preuve par la suppression
# 1) créez des données (via psql, une app…), puis :
docker rm -f db                 # on DÉTRUIT le conteneur

# 2) on le recrée, branché sur le MÊME volume
docker run -d --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16
# → vos données sont toujours là. 
bash · gérer les volumes
docker volume ls              # lister les volumes
docker volume inspect pgdata  # détails d'un volume
docker volume rm pgdata       # supprimer (attention : efface les données !)

Volume nommé vs bind mount

Il existe deux façons de persister des données. À ne pas confondre :

Volume nomméBind mount
Syntaxe-v pgdata:/chemin-v ./mon-dossier:/chemin
EmplacementGéré par Docker (vous n'avez pas à savoir où).Un dossier précis de votre machine.
Idéal pourLes données d'application (bases, uploads) en dev comme en prod.Le code source en développement (voir ci-dessous), ou des fichiers de config.
Le bind mount, ami du développeur

En montant votre dossier de code (-v ./src:/app/src), vos modifications locales sont immédiatement visibles dans le conteneur — pratique pour développer sans reconstruire l'image à chaque changement. Pour les données (une base), préférez un volume nommé : plus portable et géré par Docker.

Si ça ne fonctionne pas

Mes données disparaissent encore malgré -v

Cause fréquente : le chemin interne est faux. Le volume doit pointer exactement sur le dossier où le logiciel écrit (pour PostgreSQL : /var/lib/postgresql/data). Un chemin erroné persiste… le mauvais dossier.
Vérification : consultez la documentation de l'image (Docker Hub indique le chemin des données), ou docker inspect.

Challenge

Créez un volume, écrivez-y un fichier depuis un conteneur (docker exec ... sh -c 'echo hello > /data/test.txt' avec -v monvol:/data), supprimez le conteneur, puis relancez-en un autre branché sur le même volume et vérifiez que test.txt existe toujours. Vous aurez prouvé la persistance de A à Z.

En résumé
  • Le système de fichiers d'un conteneur est éphémère : docker rm efface tout ce qu'il contient.
  • Un volume stocke les données hors du conteneur ; elles survivent à sa suppression.
  • Syntaxe : -v nom_volume:/chemin/dans/le/conteneur. Pour PostgreSQL : /var/lib/postgresql/data.
  • Volume nommé pour les données ; bind mount pour monter du code ou de la config depuis votre machine.
7
Partie 7

Variables et configuration

Une même image doit pouvoir tourner en développement, en test et en production sans être reconstruite. Le secret : sortir la configuration du code. Et surtout, apprendre à ne jamais exposer un mot de passe.

Partie 7 · Chapitre 14

Variables d'environnement, configuration & secrets

À la fin de ce chapitre, vous serez capable de…
  • configurer un conteneur avec -e et un fichier .env ;
  • distinguer configuration, credentials et secrets ;
  • adapter une même image à dev, test et prod ;
  • éviter la faute grave : un mot de passe écrit dans un Dockerfile ou poussé dans Git.

Le principe : la config vient de l'extérieur

Une bonne image est générique. Ce qui la spécialise pour un environnement donné — l'URL de la base, une clé d'API, un niveau de log — lui est fourni au démarrage, via des variables d'environnement. Ainsi la même image passe de votre poste à la production sans modification.

Trois façons de passer des variables

1) Une par une avec -e

bash
docker run -e APP_ENV=production -e LOG_LEVEL=info mon-app

2) En lot avec un fichier .env

Dès qu'il y a plusieurs variables, on les regroupe dans un fichier .env :

.env
APP_ENV=production
LOG_LEVEL=info
DATABASE_URL=postgresql://tasks:secret@db:5432/tasks
API_KEY=sk-remplacez-moi
bash · charger le fichier
docker run --env-file .env mon-app

3) Avec Docker Compose (aperçu)

C'est l'approche que vous utiliserez le plus. On y arrive dès la Partie 8 :

docker-compose.yml · extrait
services:
  api:
    image: mon-app
    environment:
      APP_ENV: production
    env_file:
      - .env

Configuration, credentials, secrets : ne pas tout mélanger

TypeExemplesSensibilité
ConfigurationNiveau de log, langue, nom de l'environnement, options.Faible — peut vivre en clair dans le dépôt.
CredentialsUtilisateur/mot de passe de base, URL avec identifiants.Élevée — jamais dans Git.
SecretsClés d'API, jetons, clés privées, certificats.Critique — jamais dans Git, ni dans l'image.
La faute grave à ne JAMAIS commettre

N'écrivez jamais un mot de passe ou une clé en dur dans un Dockerfile :

#  CATASTROPHIQUE
ENV DATABASE_PASSWORD=SuperSecret123

Tout ce qui est dans un Dockerfile se retrouve figé dans les couches de l'image : n'importe qui ayant l'image peut lire le secret (docker history le révèle), même si vous « écrasez » la variable plus tard. Un secret dans une image est un secret public.

Bonnes pratiques

Fournissez les secrets au démarrage (via -e, --env-file, ou un gestionnaire de secrets), jamais à la construction. Ajoutez .env à votre .gitignore et à votre .dockerignore. Versionnez plutôt un fichier d'exemple .env.example sans valeurs réelles, pour documenter les variables attendues.

Une image, trois environnements

La même image mon-app, spécialisée par ses variables :

EnvironnementExemple de variables
DéveloppementAPP_ENV=dev, LOG_LEVEL=debug, base locale.
TestAPP_ENV=test, base de test jetable.
ProductionAPP_ENV=production, LOG_LEVEL=warn, secrets injectés par la plateforme.
Pourquoi c'est un tel gain

Vous testez exactement l'image qui partira en production — seule la configuration change. Fini les « ça marchait en test » dus à une image reconstruite différemment. C'est l'un des grands principes des applications modernes.

Si ça ne fonctionne pas

Ma variable n'est pas prise en compte

Vérifiez : le nom est-il exactement celui attendu par l'app (sensible à la casse) ? Le --env-file pointe-t-il sur le bon fichier ? Attention : une variable définie par ENV dans le Dockerfile peut être écrasée au run — l'ordre de priorité va du plus proche du démarrage (le -e gagne).

Challenge

Reprenez votre API FastAPI (Chapitre 10). Ajoutez-lui une variable APP_ENV lue au démarrage et renvoyée sur une route /info. Lancez deux conteneurs de la même image avec -e APP_ENV=dev et -e APP_ENV=production, sur deux ports différents. Constatez : une image, deux comportements, zéro rebuild.

En résumé
  • La configuration vient de l'extérieur, au démarrage : -e, --env-file .env, ou Compose.
  • Une même image sert dev, test et prod : seules les variables changent.
  • Ne mettez jamais de secret dans un Dockerfile ni dans l'image : il devient public.
  • .env dans .gitignore et .dockerignore ; versionnez un .env.example sans valeurs.
8
Partie 8

Docker Compose

Tout ce que vous avez appris — images, réseaux, volumes, variables — va enfin s'assembler. Au lieu d'enchaîner dix commandes fragiles, vous décrirez toute votre application dans un seul fichier, et la lancerez d'une commande.

Partie 8 · Chapitre 15

Pourquoi Docker Compose ?

À la fin de ce chapitre, vous serez capable de…
  • expliquer le problème que Compose résout ;
  • lire et écrire un docker-compose.yml ;
  • utiliser services, ports, environment, volumes, networks, depends_on et healthcheck ;
  • piloter une application avec docker compose up et down.

Le problème : trop de commandes à la main

Au Chapitre 8, faire tourner une API + une base demandait déjà : créer un réseau, lancer la base avec ses variables et son volume, lancer l'API avec ses variables et son port… Ajoutez un cache Redis et un frontend, et vous voilà à jongler avec six commandes longues, dans le bon ordre, à retaper à chaque fois. C'est fastidieux, source d'erreurs, et impossible à partager proprement avec un collègue.

L'idée de Compose

Et si on décrivait toute l'application — ses services, leurs réseaux, volumes et variables — dans un simple fichier texte, une fois pour toutes ? Il suffirait ensuite de dire « lance tout ça ». Ce fichier, c'est docker-compose.yml ; cette commande, c'est docker compose up.

flowchart LR
  Y[" docker-compose.yml<br/>(décrit toute l'app)"] -->|docker compose up| E[" frontend<br/> backend<br/> postgres<br/> redis"]
  E -.->|réseau, volumes, variables| E
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Un seul fichier décrit l'application entière ; une seule commande la démarre — réseau et volumes compris.

Anatomie d'un docker-compose.yml

C'est un fichier YAML (indentation à base d'espaces, jamais de tabulations). Commençons par un cas simple : une API et sa base.

docker-compose.yml · une API + PostgreSQL
services:                       # la liste de vos conteneurs
  api:                          # 1er service, nommé « api »
    build: .                    # construit l'image depuis le Dockerfile local
    ports:
      - "8000:8000"            # publie le port (hôte:conteneur)
    environment:
      DATABASE_URL: postgresql://tasks:secret@db:5432/tasks
    depends_on:
      - db                      # démarre après « db »

  db:                           # 2e service, nommé « db »
    image: postgres:16          # utilise une image existante
    environment:
      POSTGRES_DB: tasks
      POSTGRES_USER: tasks
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data   # persistance (Ch.13)

volumes:                        # déclaration des volumes nommés
  pgdata:
Ce que Compose fait pour vous, gratuitement

Compose crée automatiquement un réseau pour ces services : ils se joignent donc par leur nom (db, api) sans que vous ayez à faire docker network create. Voilà pourquoi l'URL contient @db:5432 — le nom du service est le nom d'hôte (tout le Chapitre 12, appliqué automatiquement).

Les clés à connaître

CléRôle
imageUtiliser une image existante (ex. postgres:16).
buildConstruire l'image depuis un Dockerfile (ex. build: .).
portsPublier des ports vers l'hôte ("8000:8000").
environment / env_filePasser des variables, en clair ou depuis un fichier .env.
volumesMonter des volumes ou des dossiers pour la persistance.
depends_onOrdonner le démarrage (et, avec une condition, attendre qu'un service soit « sain »).
networksRéseaux personnalisés (optionnel : un réseau par défaut est déjà créé).
healthcheckTest de « bonne santé » d'un service (voir ci-dessous).

depends_on et healthcheck : démarrer dans le bon ordre

Un piège classique : l'API démarre plus vite que PostgreSQL et tente de se connecter à une base pas encore prête. depends_on seul garantit l'ordre de démarrage, mais pas que la base soit prête à répondre. La solution complète combine un healthcheck sur la base et une condition sur le depends_on :

docker-compose.yml · attendre que la base soit réellement prête
services:
  api:
    build: .
    depends_on:
      db:
        condition: service_healthy   # attend que db soit « healthy »

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5
Ce que ça résout concrètement

Avec condition: service_healthy, l'API n'est lancée que lorsque la commande pg_isready confirme que PostgreSQL accepte les connexions. Fini le « connection refused » aléatoire au démarrage, que vous aviez rencontré au Chapitre 8.

Les commandes Compose essentielles

CommandeRôle
docker compose upDémarre tous les services (logs affichés en direct).
docker compose up -dDémarre en arrière-plan (detached).
docker compose up --buildReconstruit les images avant de démarrer.
docker compose psListe l'état des services du projet.
docker compose logs -fSuit les logs de tous les services (ou d'un seul : logs -f api).
docker compose downArrête et supprime conteneurs et réseau.
docker compose down -vIdem + supprime les volumes (efface les données !).
docker compose (V2), pas docker-compose

La syntaxe moderne est docker compose (deux mots, intégré à Docker). L'ancien binaire séparé docker-compose (avec un tiret) appartient à la V1, désormais obsolète. On n'écrit plus non plus la ligne version: en haut du fichier : elle n'est plus nécessaire.

Si ça ne fonctionne pas

« yaml: line X: did not find expected key »

Cause quasi certaine : un problème d'indentation. YAML est très strict : uniquement des espaces (jamais de tabulations), et une hiérarchie régulière (2 espaces par niveau).
Solution : vérifiez l'alignement, remplacez toute tabulation par des espaces. Un éditeur qui affiche les caractères invisibles aide beaucoup.

Challenge

Reprenez l'API FastAPI du Chapitre 10 et écrivez un docker-compose.yml qui la démarre avec une variable APP_ENV=dev et le port publié. Lancez docker compose up, testez, puis docker compose down. Comparez l'effort avec les commandes manuelles : c'est le déclic Compose.

En résumé
  • Compose décrit toute une application dans un fichier YAML et la lance d'une commande.
  • Chaque service est un conteneur ; Compose crée un réseau automatique où les services se joignent par leur nom.
  • depends_on + healthcheck (condition: service_healthy) garantissent un démarrage dans le bon ordre.
  • docker compose up -d pour démarrer, logs -f pour observer, down pour tout arrêter.
  • Syntaxe moderne : docker compose (V2), sans clé version:.
Partie 8 · Chapitre 16

Projet complet avec Docker Compose

À la fin de ce chapitre, vous serez capable de…
  • orchestrer une application à quatre services avec un seul fichier ;
  • relier frontend, backend, base de données et cache ;
  • appliquer réseaux, volumes, variables et healthchecks ensemble ;
  • lancer toute l'application avec docker compose up.
Mini-projet — « Livre d'or »

Une petite application où les visiteurs laissent un message. Le backend stocke les messages dans PostgreSQL et compte les visites dans Redis ; le frontend les affiche. Quatre conteneurs, orchestrés par Compose.

Architecture

flowchart TB
  U["Navigateur"] -->|localhost:8080| F[" frontend<br/>Nginx"]
  F -->|/api → | B[" backend<br/>FastAPI"]
  B -->|messages| P[" db<br/>PostgreSQL"]
  B -->|compteur de visites| R[" cache<br/>Redis"]
  P --> V[" volume pgdata"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Quatre services sur un réseau Compose : frontend, backend, base et cache.

Technologies & prérequis

FastAPI (backend), PostgreSQL, Redis, Nginx (frontend statique). Prérequis : Docker et Docker Compose installés (Partie 2). Aucun autre outil : tout tourne en conteneurs.

Structure du projet

livre-dor/ ├── backend/ │ ├── main.py │ ├── requirements.txt │ └── Dockerfile ├── frontend/ │ ├── index.html │ └── Dockerfile ├── .env └── docker-compose.yml

Le backend

backend/requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
psycopg[binary]==3.2.1
redis==5.0.8
backend/main.py
import os
import psycopg
import redis
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

DATABASE_URL = os.environ["DATABASE_URL"]
redis_client = redis.from_url(os.environ["REDIS_URL"], decode_responses=True)

def init_db():
    with psycopg.connect(DATABASE_URL) as conn:
        conn.execute("CREATE TABLE IF NOT EXISTS messages (id SERIAL PRIMARY KEY, texte TEXT)")

@app.on_event("startup")
def startup():
    init_db()

class Message(BaseModel):
    texte: str

@app.get("/api/messages")
def lister():
    visites = redis_client.incr("visites")          # compteur dans Redis
    with psycopg.connect(DATABASE_URL) as conn:
        rows = conn.execute("SELECT texte FROM messages ORDER BY id DESC").fetchall()
    return {"visites": visites, "messages": [r[0] for r in rows]}

@app.post("/api/messages")
def ajouter(m: Message):
    with psycopg.connect(DATABASE_URL) as conn:
        conn.execute("INSERT INTO messages (texte) VALUES (%s)", (m.texte,))
    return {"status": "ok"}
backend/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Le frontend

frontend/index.html · minimal, appelle l'API
<!doctype html>
<html lang="fr">
<head><meta charset="utf-8"><title>Livre d'or</title></head>
<body>
  <h1>Livre d'or </h1>
  <input id="txt" placeholder="Votre message">
  <button onclick="envoyer()">Envoyer</button>
  <p id="visites"></p>
  <ul id="liste"></ul>
  <script>
    const API = "http://localhost:8000";   // le backend, publié sur 8000
    async function charger() {
      const r = await fetch(API + "/api/messages");
      const d = await r.json();
      visites.textContent = "Visites : " + d.visites;
      liste.innerHTML = d.messages.map(m => "<li>" + m + "</li>").join("");
    }
    async function envoyer() {
      await fetch(API + "/api/messages", {method:"POST",
        headers:{"Content-Type":"application/json"},
        body: JSON.stringify({texte: txt.value})});
      txt.value = ""; charger();
    }
    charger();
  </script>
</body></html>
frontend/Dockerfile · sert le fichier statique
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80

La configuration

.env
POSTGRES_DB=guestbook
POSTGRES_USER=guest
POSTGRES_PASSWORD=secret

Le fichier d'orchestration

docker-compose.yml · les quatre services
services:
  frontend:
    build: ./frontend
    ports:
      - "8080:80"
    depends_on:
      - backend

  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      REDIS_URL: redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
      interval: 5s
      timeout: 3s
      retries: 5

  cache:
    image: redis:7

volumes:
  pgdata:
Lisez ce fichier comme une carte de l'application

Tout ce que vous avez appris est là : quatre services ; des variables tirées du .env (${...}) ; le backend qui joint la base par le nom db et le cache par cache (Chapitre 12) ; un volume pgdata pour la persistance (Chapitre 13) ; un healthcheck pour attendre PostgreSQL (Chapitre 15). Aucun docker network create : Compose s'en charge.

Étapes & commandes

bash · tout lancer depuis le dossier livre-dor/
docker compose up --build      # construit et démarre les 4 services
# (ajoutez -d pour l'arrière-plan)

Tests & résultat attendu

  • Ouvrez http://localhost:8080 : le Livre d'or s'affiche.
  • Saisissez un message, cliquez « Envoyer » : il apparaît dans la liste, et le compteur de visites augmente à chaque chargement.
  • Vérifiez la persistance : docker compose down (sans -v), puis docker compose up — vos messages sont toujours là (volume pgdata).
bash · observer
docker compose ps            # les 4 services et leur état
docker compose logs -f backend

Erreurs possibles

  • Le backend démarre avant la base → grâce à condition: service_healthy, ça n'arrive pas ; si vous l'enlevez, vous verrez le « connection refused » du Chapitre 8.
  • Le frontend n'atteint pas l'API → ici le navigateur appelle localhost:8000 (port publié), pas backend:8000 : le nom de service ne vaut qu'entre conteneurs, jamais depuis le navigateur.
  • Port 8080 ou 8000 déjà pris → changez la partie gauche des ports.
  • Erreur CORS dans la console du navigateur → assurée par le middleware CORS du backend.
Challenge bonus

Ajoutez un cinquième comportement : une route DELETE /api/messages qui vide la table, et un bouton correspondant dans le frontend. Puis remplacez le compteur Redis par un « nombre de messages postés aujourd'hui ». Vous manipulerez les quatre services ensemble — exactement comme sur un vrai projet.

En résumé
  • Un seul docker-compose.yml orchestre frontend, backend, base et cache.
  • Les services communiquent par leur nom ; Compose crée le réseau automatiquement.
  • Variables (.env), volumes (persistance) et healthchecks (ordre de démarrage) coexistent naturellement.
  • docker compose up --build démarre tout ; down arrête tout ; down -v efface aussi les données.
  • Le nom de service ne vaut qu'entre conteneurs ; le navigateur, lui, utilise les ports publiés.
9
Partie 9

Debugging et problèmes réels

Avec Docker, quelque chose casse toujours un jour. La différence entre un débutant et un pro n'est pas d'éviter les pannes : c'est de savoir les diagnostiquer méthodiquement. Ce chapitre vous en donne la méthode.

Partie 9 · Chapitre 17

Déboguer Docker

À la fin de ce chapitre, vous serez capable de…
  • appliquer une méthode systématique de diagnostic ;
  • reconnaître les dix pannes Docker les plus fréquentes et leur cause ;
  • utiliser ps, logs, inspect comme des instruments de mesure ;
  • résoudre seul un conteneur qui ne démarre pas ou ne répond pas.

La bonne attitude

Face à une panne, le réflexe débutant est de tout relancer au hasard. Le réflexe pro est d'observer avant d'agir. Docker vous donne des instruments précis : utilisez-les dans l'ordre, et la cause se révèle presque toujours en quelques minutes.

La méthode « Diagnostic en 5 minutes »

Une procédure à suivre dans l'ordre, à chaque fois. Elle couvre l'immense majorité des cas.

flowchart TD
  S([" Quelque chose ne marche pas"]) --> A["1 · docker ps -a<br/>Le conteneur tourne-t-il ?"]
  A --> B["2 · docker logs <nom><br/>Que dit l'application ?"]
  B --> C["3 · docker inspect <nom><br/>Config, réseau, variables"]
  C --> D["4 · Vérifier les PORTS"]
  D --> E["5 · Vérifier le RÉSEAU"]
  E --> F["6 · Vérifier les VARIABLES"]
  F --> G["7 · Vérifier les VOLUMES"]
  G --> R([" Cause identifiée"])
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Toujours du plus général (le conteneur tourne-t-il ?) au plus précis (volumes). On ne saute pas d'étape.
  1. docker ps -a — Le conteneur est-il « Up » ou « Exited » ? S'il est arrêté, l'étape 2 dira pourquoi.
  2. docker logs <nom>L'instrument n°1. L'application écrit presque toujours la cause de son crash ici.
  3. docker inspect <nom> — La configuration réelle : réseaux, ports, variables, montages. Idéal pour repérer un écart avec ce que vous croyiez.
  4. Ports — Le port publié correspond-il au port de l'application (Chapitre 4) ?
  5. Réseau — Les conteneurs qui doivent se parler sont-ils sur le même réseau ? Utilisent-ils le bon nom (Chapitre 12) ?
  6. Variables — Les variables attendues sont-elles bien passées, au bon nom (Chapitre 14) ?
  7. Volumes — Le bon chemin est-il monté (Chapitre 13) ?

Catalogue des dix pannes les plus fréquentes

SymptômeCause probableSolution
port is already allocatedLe port hôte est déjà pris.Changez la partie gauche de -p, ou arrêtez l'autre conteneur.
Le conteneur s'arrête aussitôtLe programme principal s'est terminé ou a planté.docker logs : lisez l'erreur. Un service doit rester en écoute.
connection refused vers la baseMauvais hôte (localhost), pas le même réseau, ou base pas prête.Nom du conteneur + même réseau + healthcheck (Ch.12, 15).
could not translate host nameLe nom de service/conteneur est faux ou hors réseau.Vérifiez le nom exact et le réseau partagé.
pull access denied / not foundNom d'image ou tag erroné, ou image privée.Corrigez le nom/tag ; docker login si privée (Ch.21).
Erreur pendant docker buildInstruction Dockerfile invalide, ou fichier absent du contexte.Lisez l'étape qui échoue ; vérifiez le contexte de build (Ch.6).
permission deniedDroits insuffisants (socket Docker, ou fichier en non-root).Groupe docker (Ch.3) ; droits du dossier monté.
Variable ignorée / valeur nulleNom de variable erroné ou non transmis.Vérifiez la casse et --env-file (Ch.14).
Données perdues au redémarragePas de volume, ou mauvais chemin monté.Montez un volume sur le bon dossier (Ch.13).
App joignable en local mais pas via -pApp en écoute sur 127.0.0.1 seulement.Écoutez sur 0.0.0.0 (Ch.10).

Trois commandes de diagnostic avancé

bash
docker exec -it <nom> sh          # entrer dans le conteneur et explorer
docker inspect <nom> | grep -i ip  # trouver l'IP interne, le réseau
docker compose logs -f             # suivre TOUS les services d'un coup
Déboguer « de l'intérieur »

Quand un conteneur ne joint pas un autre, entrez dedans (docker exec -it) et testez depuis là : ping db, ou installez un outil réseau minimal. Vous voyez le monde exactement comme le voit l'application — souvent, la cause saute aux yeux.

Exercices

Exercice 17.1 Débutant

Un conteneur apparaît en Exited (1) dans docker ps -a. Quelle est la toute première commande à lancer, et pourquoi ?

Voir la correction

docker logs <nom>. Un code de sortie 1 signale une erreur ; l'application a presque certainement écrit la raison dans ses logs avant de mourir. On lit avant de supposer.

Exercice 17.2 Intermédiaire

Votre backend renvoie connection refused vers db. Déroulez la méthode « 5 minutes » : listez, dans l'ordre, les trois vérifications les plus probables.

Voir la correction

(1) Logs du backend et de db — la base est-elle prête ? (2) Réseau — les deux sont-ils sur le même réseau (docker network inspect / même projet Compose) ? (3) Config — l'URL pointe-t-elle sur db (nom du service) et non localhost ? Bonus : un healthcheck évite la course au démarrage.

Challenge Pro

Cassez volontairement le projet « Livre d'or » du Chapitre 16 de trois façons (mauvais nom d'hôte de base ; port publié erroné ; variable de mot de passe supprimée). Pour chacune, prédisez le symptôme avant de lancer, puis vérifiez avec la méthode. Diagnostiquer sur une panne que l'on a créée soi-même est le meilleur entraînement qui soit.

En résumé
  • Observer avant d'agir : suivez la méthode ps → logs → inspect → ports → réseau → variables → volumes.
  • docker logs est l'instrument n°1 : l'application y écrit presque toujours la cause.
  • La majorité des pannes se rangent dans le catalogue des dix ; apprenez à les reconnaître.
  • En cas de doute réseau, déboguez depuis l'intérieur du conteneur avec docker exec -it.
10
Partie 10

Docker avancé

Trois compétences qui font passer vos images de « ça marche » à « c'est propre » : construire en plusieurs étapes, alléger, et sécuriser. Ce sont exactement les points que l'on regarde en revue de code professionnelle.

Partie 10 · Chapitre 18

Multi-stage builds

À la fin de ce chapitre, vous serez capable de…
  • expliquer pourquoi une image « tout-en-un » est un problème ;
  • écrire un build multi-étapes pour Java et Node.js ;
  • ne conserver dans l'image finale que le strict nécessaire à l'exécution ;
  • mesurer le gain en taille et en sécurité.

Le problème : les outils de build n'ont rien à faire en production

Vous l'avez déjà entrevu au Chapitre 11. Pour construire une application, il faut souvent un lourd outillage : un JDK complet et Maven pour Java, Node et des centaines de node_modules pour le frontend. Mais pour exécuter le résultat, il ne faut qu'un JRE, ou qu'un serveur web. Garder l'outillage de build dans l'image finale, c'est trimballer une caisse à outils entière pour juste faire tourner l'objet fini.

flowchart LR
  subgraph BAD [" Une seule étape"]
    direction TB
    b1["JDK + Maven + code + sources + .jar"] --> b2["Image énorme<br/>(> 700 Mo)"]
  end
  subgraph GOOD [" Multi-stage"]
    direction TB
    g1["Étape build :<br/>JDK + Maven → .jar"] --> g2["Étape runtime :<br/>JRE + .jar seul"]
    g2 --> g3["Image légère<br/>(~200 Mo)"]
  end
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
  style BAD fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
  style GOOD fill:#EDF2FB,stroke:#B7C6DE,color:#12213A;
Le multi-stage jette l'outillage de build et ne garde que le résultat exécutable.

Le principe : plusieurs FROM, une seule image finale

Un Dockerfile peut enchaîner plusieurs étapes, chacune démarrant par un FROM. On nomme les étapes avec AS, et on copie sélectivement d'une étape à l'autre avec COPY --from=. Seule la dernière étape constitue l'image finale ; tout le reste est jeté.

Exemple Java (Spring Boot) — mieux qu'au Chapitre 8

Dockerfile · Docker construit lui-même le .jar
# ---------- Étape 1 : build ----------
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn dependency:go-offline        # dépendances en cache
COPY src ./src
RUN mvn clean package -DskipTests    # produit /src/target/*.jar

# ---------- Étape 2 : runtime ----------
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /src/target/*.jar app.jar
RUN useradd --create-home appuser
USER appuser
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Le grand avantage

L'image finale ne contient ni Maven, ni le JDK, ni les sources — juste un JRE et le .jar. Plus légère, plus rapide à démarrer, et plus sûre (moins d'outils = moins de surface d'attaque). En prime, plus besoin de construire le jar à la main avant : Docker fait tout, de façon reproductible.

Exemple Node.js (frontend) — rappel

C'est exactement le patron du Chapitre 11 : étape node pour npm run build, puis étape nginx:alpine qui reçoit seulement le dossier de sortie via COPY --from=build.

Bénéfices, en résumé

BénéficePourquoi
TailleOn jette l'outillage de build (souvent des centaines de Mo).
SécuritéMoins de logiciels dans l'image = surface d'attaque réduite.
PerformanceImage plus petite = téléchargement et démarrage plus rapides.
ClartéSéparation nette entre « construire » et « exécuter ».
Challenge

Prenez une de vos images Node ou Java existantes en une seule étape. Réécrivez-la en multi-stage, puis comparez les tailles avec docker images. Notez l'écart : c'est du concret que vous pourrez mettre en avant en entretien ou en revue de code.

En résumé
  • Le multi-stage sépare l'étape de build (riche) de l'étape de runtime (minimale).
  • On nomme les étapes avec AS et on récupère le résultat avec COPY --from=.
  • Seule la dernière étape devient l'image ; l'outillage de build est jeté.
  • Gains : taille, sécurité, performance, clarté.
Partie 10 · Chapitre 19

Optimisation des images

À la fin de ce chapitre, vous serez capable de…
  • réduire significativement la taille de vos images ;
  • accélérer vos builds en exploitant le cache de couches ;
  • appliquer une liste de leviers concrets, avec un avant/après ;
  • mesurer avant d'optimiser.

Pourquoi optimiser ?

Une image légère se télécharge plus vite, démarre plus vite, coûte moins cher à stocker, se déploie plus souvent, et présente moins de failles. Ce n'est pas de la coquetterie : en production et en CI/CD, la taille des images a un impact réel sur la vitesse et la facture.

D'abord mesurer

Avant toute optimisation, regardez la réalité : docker images pour la taille totale, docker history <image> pour voir quelle couche pèse lourd. On optimise ce qui compte, pas au hasard.

Les leviers, du plus efficace au plus fin

LevierEffet
Image de base légère (-slim, alpine)Souvent le plus gros gain d'un coup (Ch.7).
Multi-stage buildJette l'outillage de build (Ch.18).
Ordre des instructionsDépendances avant le code → cache réutilisé (Ch.7).
.dockerignoreExclut fichiers inutiles et lourds du contexte (Ch.7).
Nettoyer dans la même coucheSupprimer caches/temporaires dans le RUN qui les crée.
Regrouper les RUNMoins de couches, pas de fichiers temporaires laissés entre elles.

Avant / après : un cas concret

Avant

Dockerfile · gaspilleur
FROM python:3.12                 # image complète, lourde
WORKDIR /app
COPY . .                          # code copié avant les deps → casse le cache
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl   # caches apt laissés dans la couche

Après

Dockerfile · optimisé
FROM python:3.12-slim            # base légère
WORKDIR /app

# Dépendances d'abord (cache), sans cache pip conservé
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Un seul RUN : install + nettoyage dans la MÊME couche
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*

# Le code en dernier
COPY . .
Le détail qui change tout : nettoyer dans la même couche

Chaque couche est figée. Si vous installez dans un RUN et nettoyez dans un RUN suivant, les fichiers supprimés restent présents dans la couche précédente : l'image ne maigrit pas. Il faut installer et nettoyer dans le même RUN (avec &&) pour que la couche soit propre dès sa création.

N'optimisez pas au détriment de la lisibilité

Regrouper les RUN est utile, mais un Dockerfile illisible est un Dockerfile dangereux. Cherchez l'équilibre : quelques RUN logiques et commentés valent mieux qu'un seul bloc géant illisible ou que vingt couches inutiles.

Challenge

Reprenez une image existante et appliquez, une par une, les optimisations du tableau. Notez la taille après chaque changement avec docker images. Quel levier vous a fait gagner le plus ? (Indice : c'est souvent le choix de l'image de base.)

En résumé
  • Optimiser = images plus rapides à télécharger, démarrer et déployer, et plus sûres.
  • Mesurez d'abord avec docker images et docker history.
  • Plus gros leviers : base légère, multi-stage, ordre pour le cache, .dockerignore.
  • Installez et nettoyez dans le même RUN, sinon l'image ne maigrit pas.
Partie 10 · Chapitre 20

Sécurité Docker

À la fin de ce chapitre, vous serez capable de…
  • appliquer les bonnes pratiques de sécurité essentielles à vos images ;
  • éviter les erreurs de sécurité les plus courantes ;
  • scanner une image à la recherche de vulnérabilités ;
  • appliquer le principe du moindre privilège.
Le périmètre de ce chapitre

Objectif : les réflexes de sécurité utiles au quotidien d'un développeur, pas un cours de cybersécurité. Quelques habitudes bien tenues éliminent l'essentiel des risques courants.

1. Ne pas tourner en root

Le réflexe déjà vu au Chapitre 7, et le plus important. Par défaut, un conteneur s'exécute en root ; une faille exploitée hérite alors de droits élevés. Créez un utilisateur dédié et basculez dessus :

Dockerfile
RUN useradd --create-home appuser
USER appuser

2. Partir d'images de confiance

Une image, c'est du code exécuté sur vos machines. Préférez les images officielles ou d'éditeurs vérifiés, et fixez les versions (pas de latest) pour des builds reproductibles et audités. Une image obscure tirée au hasard peut contenir n'importe quoi.

3. Mettre à jour les dépendances

Une image « qui marche » vieillit : les vulnérabilités sont découvertes après coup. Reconstruisez régulièrement pour récupérer les correctifs de l'image de base et de vos dépendances. Une image figée pendant deux ans est une image à trous.

4. Ne jamais embarquer de secrets

On l'a martelé au Chapitre 14 : aucun mot de passe ni clé dans le Dockerfile ou l'image (révélés par docker history). Les secrets se fournissent au démarrage, et .env reste dans .gitignore et .dockerignore.

Le classique qui fuite

Copier tout le dossier avec COPY . . sans .dockerignore, et embarquer par accident un .env, une clé SSH ou le dossier .git. Vérifiez toujours ce que votre image contient réellement.

5. Réduire la surface d'attaque

Moins il y a de logiciels dans une image, moins il y a de failles possibles. C'est un bénéfice gratuit des chapitres précédents : image -slim/alpine (Ch.19) et multi-stage (Ch.18) réduisent mécaniquement la surface d'attaque. N'installez que le strict nécessaire.

6. Scanner ses images

Des outils analysent une image et listent ses vulnérabilités connues. Intégré à Docker, Docker Scout ; très répandu aussi, Trivy :

bash · scanner une image
docker scout cves mon-app        # vulnérabilités connues (Docker Scout)
# ou avec Trivy :
trivy image mon-app
Bonne pratique

Intégrez un scan à votre chaîne CI/CD (Chapitre 22) : chaque image est vérifiée automatiquement avant d'être publiée. La sécurité devient un filet permanent, pas une action ponctuelle qu'on oublie.

7. Le principe du moindre privilège

Le fil rouge de tout le chapitre : chaque composant ne reçoit que les droits dont il a strictement besoin. Utilisateur non-root, ports non publiés inutilement (Ch.12), pas de secrets superflus, image minimale. En cas de compromission, les dégâts restent contenus.

Récapitulatif : la mini-checklist sécurité

À vérifier avant de publier une image

Utilisateur non-root · image officielle et versionnée · dépendances à jour · aucun secret embarqué · .dockerignore en place · image minimale (slim/multi-stage) · image scannée. On la reverra, complète, dans la checklist professionnelle du Chapitre 32.

Challenge Pro

Scannez une de vos images avec docker scout cves ou trivy image. Identifiez une vulnérabilité liée à l'image de base, puis tentez de la corriger en passant à une base plus récente ou plus minimale. Re-scannez et comparez. Vous venez de faire de la vraie remédiation de sécurité.

En résumé
  • Non-root par défaut, images de confiance et versionnées, dépendances à jour.
  • Aucun secret dans une image ; ils sont fournis au démarrage.
  • Réduire la surface d'attaque (slim, multi-stage) et scanner ses images (Docker Scout, Trivy).
  • Fil conducteur : le moindre privilège — juste ce qu'il faut, rien de plus.
11
Partie 11

Docker en équipe et en production

Une image sur votre machine ne sert à personne d'autre. Vous allez apprendre à la publier, à automatiser sa construction avec le CI/CD, et à comprendre comment on la déploie sur un vrai serveur.

Partie 11 · Chapitre 21

Docker Hub et Registry

À la fin de ce chapitre, vous serez capable de…
  • publier une image sur un registry et la récupérer ailleurs ;
  • utiliser login, tag, push, pull ;
  • nommer et versionner correctement vos images ;
  • comprendre l'intérêt d'un registry privé.

Le concept : le registry, l'entrepôt d'images partagé

Jusqu'ici, vos images vivaient sur votre machine. Pour qu'un collègue ou un serveur les utilise, il faut les déposer dans un registry — un entrepôt d'images accessible à distance. Vous en connaissez déjà un : Docker Hub, d'où vous tirez postgres ou nginx. Vous pouvez y déposer les vôtres.

flowchart LR
  A["Votre machine<br/>image construite"] -->|docker push| R[" Registry<br/>(Docker Hub / privé)"]
  R -->|docker pull| B["Machine d'un collègue"]
  R -->|docker pull| C["Serveur de production"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
On pousse une fois, on tire partout : c'est ainsi qu'une image voyage vers les collègues et la production.

Le cycle push / pull, en pratique

bash · 1) s'authentifier
docker login                     # demande identifiant + mot de passe/token
bash · 2) étiqueter l'image au bon nom
# Format : <utilisateur>/<repository>:<tag>
docker tag mon-app moncompte/mon-app:1.0.0
bash · 3) publier
docker push moncompte/mon-app:1.0.0
bash · 4) récupérer ailleurs
docker pull moncompte/mon-app:1.0.0
docker run moncompte/mon-app:1.0.0
Pourquoi docker tag est nécessaire

Le nom de l'image doit indiquer la pousser : votre compte / le repository. docker tag ajoute simplement ce nom complet à une image existante (sans la dupliquer). Sans ce nommage, Docker ne sait pas vers quel entrepôt envoyer l'image.

Bien versionner ses tags

Un même repository contient plusieurs versions, distinguées par leur tag. Adoptez une convention claire :

TagUsage
1.0.0, 1.2.3Versions précises et immuables — la bonne pratique en production.
1.2, 1Alias « dernière version mineure/majeure » (pratiques, moins stricts).
latestCommodité pour tester ; à éviter pour un déploiement reproductible (Ch.5).
Bonne pratique

Déployez toujours un tag de version précis (1.4.2), jamais latest. Vous saurez exactement ce qui tourne, et pourrez revenir en arrière en un instant si une version pose problème.

Registry public vs privé

Docker Hub public convient au partage ouvert. Pour du code propriétaire, on utilise un registry privé : repositories privés sur Docker Hub, ou registry des fournisseurs Cloud (GitHub Container Registry, GitLab, AWS ECR, etc.). Le fonctionnement (login/tag/push/pull) est identique — seule l'adresse du registry change.

bash · exemple avec un registry privé
docker login registry.mon-entreprise.com
docker tag mon-app registry.mon-entreprise.com/equipe/mon-app:1.0.0
docker push registry.mon-entreprise.com/equipe/mon-app:1.0.0
Challenge

Créez un compte Docker Hub gratuit, publiez l'image de votre API FastAPI (Chapitre 10) sous votrecompte/fastapi-demo:1.0.0, puis supprimez l'image locale (docker rmi) et récupérez-la depuis le Hub avec docker pull. Vous aurez bouclé le voyage complet d'une image.

En résumé
  • Un registry est l'entrepôt d'où l'on pousse et tire les images.
  • Cycle : logintag (nom complet) → pushpull ailleurs.
  • Versionnez avec des tags précis ; évitez latest en production.
  • Registry privé = même mécanique, adresse différente, pour du code non public.
Partie 11 · Chapitre 22

Docker et CI/CD

À la fin de ce chapitre, vous serez capable de…
  • expliquer ce qu'est un pipeline CI/CD et le rôle de Docker dedans ;
  • lire un workflow GitHub Actions ;
  • construire, tester et publier une image automatiquement à chaque commit ;
  • comprendre pourquoi « testé = déployé » grâce aux conteneurs.

Le concept : automatiser la chaîne du code à la production

CI/CD signifie Intégration Continue / Déploiement Continu. L'idée : dès que vous poussez du code, une chaîne automatique s'occupe de le construire, le tester, l'emballer en image et — si tout est vert — le déployer. Plus d'étapes manuelles oubliées, plus de « ça marchait sur ma machine ».

flowchart LR
  A["git push"] --> B["CI démarre"]
  B --> C[" docker build"]
  C --> D[" Tests"]
  D -->|verts| E[" push image<br/>vers le registry"]
  E --> F[" Déploiement"]
  D -->|rouges| X[" Arrêt<br/>rien n'est publié"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le pipeline : chaque commit déclenche build → tests → publication → déploiement. Si les tests échouent, tout s'arrête.
Pourquoi Docker est au cœur du CI/CD

Le conteneur garantit que l'image testée par la CI est exactement celle qui partira en production — mêmes couches, mêmes dépendances. « Testé » devient réellement « déployé ». C'est la promesse de reproductibilité du Chapitre 0, tenue jusqu'au bout.

Un pipeline concret avec GitHub Actions

GitHub Actions exécute des workflows décrits dans des fichiers YAML placés dans .github/workflows/. Voici un pipeline qui construit et publie une image à chaque push sur main.

.github/workflows/docker.yml
name: Build & Push Docker

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # 1) Récupérer le code
      - uses: actions/checkout@v4

      # 2) S'authentifier au registry (identifiants stockés en secrets)
      - name: Login Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USER }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      # 3) Construire et pousser l'image
      - name: Build & push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: moncompte/mon-app:latest,moncompte/mon-app:${{ github.sha }}
Les secrets ne s'écrivent JAMAIS dans le workflow

Notez ${{ secrets.DOCKERHUB_TOKEN }} : les identifiants sont rangés dans les secrets du dépôt (réglages GitHub), pas dans le fichier YAML versionné. C'est le prolongement direct du Chapitre 14 : aucun secret dans le code, jamais. On utilise d'ailleurs un token d'accès, pas son mot de passe principal.

Ajouter une étape de tests

Le vrai intérêt du CI, c'est de bloquer une image qui casse les tests. On insère une étape avant la publication :

.github/workflows/docker.yml · étape de tests (extrait)
      - name: Lancer les tests dans le conteneur
        run: |
          docker build -t app-test .
          docker run --rm app-test pytest      # échoue → le job s'arrête

Si pytest renvoie une erreur, GitHub Actions arrête le job : l'image défectueuse n'est jamais publiée ni déployée. Exactement le comportement voulu.

Où le scan de sécurité s'insère

Rappelez-vous le Chapitre 20 : ajoutez une étape docker scout cves ou trivy image dans le pipeline. Chaque image est ainsi automatiquement scannée avant publication — la sécurité devient un filet permanent.

Challenge Pro

Sur un dépôt GitHub de test, mettez en place ce workflow pour l'API FastAPI du Chapitre 10. Ajoutez les secrets Docker Hub, poussez un commit, et observez le pipeline construire puis publier l'image. Cassez volontairement un test et vérifiez que la publication est bien bloquée.

En résumé
  • Le CI/CD automatise build → tests → publication → déploiement à chaque commit.
  • Docker garantit que l'image testée est celle qui est déployée.
  • Un workflow GitHub Actions vit dans .github/workflows/ ; les identifiants sont des secrets, jamais dans le YAML.
  • Une étape de tests (et de scan) bloque toute image défectueuse avant publication.
Partie 11 · Chapitre 23

Docker et déploiement

À la fin de ce chapitre, vous serez capable de…
  • décrire les étapes d'un déploiement Docker sur un serveur ;
  • comprendre le rôle d'un reverse proxy et du HTTPS ;
  • gérer variables et logs en production ;
  • situer Kubernetes dans l'écosystème, sans vous y perdre.
Le périmètre

Ce chapitre donne une vue d'ensemble claire du déploiement, pas un guide d'administration système exhaustif. L'objectif : comprendre les pièces et leur rôle, pour dialoguer et déployer un projet simple.

Le principe : la même image, sur un serveur

Bonne nouvelle : déployer, c'est faire tourner votre image ailleurs. Toute la reproductibilité que vous avez construite prend ici tout son sens. Le schéma le plus courant pour une application simple :

flowchart LR
  D[" Développeur"] -->|push| R[" Registry"]
  S[" Serveur / VPS"] -->|pull| R
  U[" Internet"] -->|HTTPS 443| P[" Reverse proxy<br/>(HTTPS, routage)"]
  P --> A[" Conteneurs<br/>(compose)"]
  A --> DB[" Base + volume"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le serveur tire l'image du registry ; un reverse proxy gère le HTTPS et route vers les conteneurs.

Les étapes d'un déploiement simple

  1. Un serveur — souvent un VPS (serveur virtuel loué), avec Docker Engine installé (Chapitre 3).
  2. Récupérer l'imagedocker pull depuis votre registry, ou copier le docker-compose.yml et laisser le serveur tirer les images.
  3. Fournir la configuration — les variables et secrets via un .env présent sur le serveur (jamais dans l'image, Chapitre 14).
  4. Lancerdocker compose up -d, exactement comme en local.
  5. Exposer proprement — un reverse proxy devant, pour le HTTPS et le nom de domaine.

Le reverse proxy et le HTTPS

Vos conteneurs écoutent en HTTP sur des ports internes. Pour servir le public sur https://mon-site.com, on place devant un reverse proxy (Nginx, Traefik, Caddy…). Il s'occupe de :

  • HTTPS : chiffrer les échanges, avec un certificat souvent obtenu et renouvelé automatiquement (Let's Encrypt) ;
  • Routage : diriger mon-site.com vers le frontend, mon-site.com/api vers le backend ;
  • Point d'entrée unique : un seul port public (443), les conteneurs restent privés derrière.
Bonne pratique

Ne publiez sur Internet que le reverse proxy. Vos bases de données et services internes restent non publiés (Chapitre 12) : ils ne sont joignables que par les autres conteneurs, jamais depuis l'extérieur.

Variables et logs en production

  • Variables/secrets : fournis par le serveur (fichier .env protégé, ou gestionnaire de secrets de la plateforme). La même image qu'en test, spécialisée par sa config.
  • Logs : vos applications écrivent sur la sortie standard ; docker compose logs les consulte. En production, on les collecte souvent vers un système centralisé pour les conserver et les chercher.
  • Redémarrage : ajoutez une politique restart: unless-stopped dans Compose pour qu'un conteneur qui plante redémarre tout seul.
docker-compose.yml · extrait production
services:
  api:
    image: moncompte/mon-app:1.4.2   # version précise, pas latest
    restart: unless-stopped
    env_file: .env

Où Kubernetes s'insère-t-il ?

Vous entendrez beaucoup parler de Kubernetes. Situons-le simplement, sans entrer dans le détail (ce n'est pas l'objet de ce livre). Docker Compose suffit largement pour une application sur un serveur. Mais quand il faut faire tourner de nombreux conteneurs sur plusieurs machines, avec mise à l'échelle automatique, auto-réparation et déploiements sans coupure, on utilise un orchestrateur : Kubernetes en est le standard.

Ce qu'il faut retenir

Kubernetes orchestre des conteneurs — les mêmes images Docker que vous savez déjà construire. Vos compétences ne sont pas perdues : elles sont le prérequis de Kubernetes. Apprendre Kubernetes est une étape suivante optionnelle, à aborder seulement quand une application l'exige réellement.

Le piège classique

Ne dégainez pas Kubernetes pour un blog ou une petite application : c'est une complexité considérable, rarement justifiée au début. docker compose sur un VPS avec un reverse proxy couvre une immense majorité des besoins. « La bonne technologie, c'est la plus simple qui résout votre problème. »

Challenge Pro

Sur le papier, dessinez le déploiement du projet « Livre d'or » (Chapitre 16) sur un VPS : où va le reverse proxy, quels ports sont publics, où sont les secrets, quelle version d'image déployer, quelle politique de redémarrage. Vous verrez que vous maîtrisez déjà toutes les pièces.

En résumé
  • Déployer = faire tourner la même image sur un serveur (souvent un VPS) via docker compose up -d.
  • Un reverse proxy gère HTTPS, domaine et routage ; seuls lui et le port 443 sont publics.
  • Config par .env côté serveur ; logs sur la sortie standard ; restart: unless-stopped.
  • Kubernetes orchestre beaucoup de conteneurs sur plusieurs machines — une étape ultérieure et optionnelle, bâtie sur ce que vous savez déjà.
12
Partie 12 · Axe IA

Introduction à l'IA

Bienvenue dans le second grand axe du livre. Toutes vos compétences Docker vont maintenant servir un nouveau but : construire des applications d'intelligence artificielle fiables et reproductibles. On commence par comprendre pourquoi Docker et IA forment un duo si naturel.

Partie 12 · Chapitre 24 · Axe IA

Pourquoi Docker + IA ?

À la fin de ce chapitre, vous serez capable de…
  • expliquer les problèmes propres aux applications d'IA ;
  • comprendre pourquoi ces applications sont particulièrement fragiles à reproduire ;
  • situer le rôle de Docker face aux dépendances, aux modèles et au GPU ;
  • aborder sereinement les chapitres IA qui suivent.

Le problème : l'IA, un cauchemar de reproductibilité

Si vous avez déjà tenté de faire tourner un projet d'IA trouvé en ligne, vous connaissez la scène : des dizaines de dépendances, des versions qui se contredisent, un modèle de plusieurs gigaoctets à télécharger, et une erreur cryptique liée au GPU. Les applications d'IA cumulent tous les problèmes d'environnement du Chapitre 1, en pire.

Difficulté propre à l'IAPourquoi ça complique tout
Dépendances nombreuses et lourdesLes bibliothèques d'IA (calcul numérique, frameworks) pèsent lourd et s'imbriquent en versions précises.
Python et ses versionsL'écosystème IA est très lié à Python ; un écart de version casse facilement un projet.
Versions capricieusesUne mise à jour d'une bibliothèque peut changer silencieusement le comportement d'un modèle.
Modèles volumineuxLes fichiers de modèles se comptent en Go ; il faut décider où et comment les gérer.
Le GPUExploiter une carte graphique ajoute pilotes, CUDA et compatibilité matérielle (Chapitre 30).
Reproductibilité« Ça marchait sur le serveur d'entraînement » est encore plus douloureux quand un run coûte cher.

La réponse de Docker : figer l'environnement, une fois pour toutes

C'est exactement le problème que Docker résout, transposé à l'IA. En emballant l'application, ses dépendances précises, son runtime Python et (quand il le faut) l'accès au GPU dans une image, Docker rend l'environnement d'IA reproductible : le même comportement sur votre machine, celle d'un collègue, un serveur, ou en production.

flowchart TD
  A[" Application IA"] --> D[" Docker"]
  D --> DEP[" Dépendances figées<br/>(Python + bibliothèques)"]
  D --> MOD[" Modèle<br/>(API distante ou fichier local)"]
  D --> HW[" Matériel / GPU<br/>(accès contrôlé)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Docker encapsule les trois sources de fragilité de l'IA : dépendances, modèle et matériel.

Deux grandes façons d'utiliser un modèle d'IA

Tout au long de cet axe, vous rencontrerez deux approches — souvent combinées. Autant les nommer dès maintenant :

Appeler une API distante

Votre conteneur envoie une requête à un modèle hébergé ailleurs (un fournisseur d'IA). Simple, sans GPU chez vous, mais dépend d'un service externe et a un coût par requête. Parties 14 et 15.

Exécuter un modèle localement

Le modèle tourne dans (ou à côté de) vos conteneurs, sur votre matériel. Aucune donnée ne sort, pas de coût par requête, mais des contraintes de mémoire et de GPU. Parties 16 et 17.

Le fil conducteur : rester agnostique

Nous construirons du code qui ne dépend pas d'un seul fournisseur. Grâce à un standard répandu (l'API « compatible OpenAI », Chapitre 26), le même code pourra parler à différents fournisseurs ou à un modèle local, en changeant juste une variable d'environnement. Vos compétences resteront valables même si un fournisseur change son offre.

Ce que ce livre ne fait pas

Nous n'entraînons pas de modèles et n'entrons pas dans les mathématiques des réseaux de neurones. L'objectif est de conteneuriser et intégrer l'IA dans des applications — pas de devenir chercheur en apprentissage automatique. Docker reste au centre.

Exercices

Exercice 24.1 Intermédiaire · Pratique

Constatez de vos yeux le « poids » des dépendances d'IA. Construisez une image qui installe une bibliothèque de calcul numérique courante et mesurez sa taille.

Dockerfile · dans un dossier vide
FROM python:3.12-slim
RUN pip install --no-cache-dir numpy
bash
docker build -t test-ia .
docker images test-ia   # notez la taille

À faire : notez la taille. Puis remplacez numpy par une dépendance plus lourde (par exemple pandas, ou une bibliothèque de machine learning), reconstruisez, et comparez. Que concluez-vous sur l'intérêt de figer tout cela dans une image plutôt que de l'installer à la main sur chaque machine ?

Voir la correction

La taille grimpe vite : les bibliothèques d'IA/data pèsent lourd et tirent de nombreuses dépendances. Sans Docker, chaque personne devrait reproduire exactement cette installation, avec les bonnes versions — source d'erreurs et de pertes de temps. Avec Docker, l'environnement est construit une fois et reproductible partout : c'est précisément la valeur de la conteneurisation pour l'IA. (Vous constaterez aussi pourquoi le choix de la base et le multi-stage, Chapitres 7 et 18, comptent double en IA.)

Challenge de réflexion

Reprenez les « trois familles de problèmes » du Chapitre 1 (versions, dépendances manquantes, configuration). Pour chacune, donnez un exemple spécifique à l'IA. Vous verrez que l'IA ne crée pas de problèmes nouveaux : elle amplifie ceux que Docker sait déjà résoudre.

En résumé
  • Les applications d'IA cumulent dépendances lourdes, versions fragiles, modèles volumineux et contraintes GPU.
  • Docker fige cet environnement et le rend reproductible — le problème du Chapitre 1, à l'échelle de l'IA.
  • Deux approches : API distante (simple, payante) ou modèle local (privé, gourmand).
  • On reste agnostique grâce au standard d'API compatible OpenAI.
13
Partie 13 · Axe IA

Première application IA conteneurisée

Assez de théorie : on construit une vraie API d'IA, conteneurisée. On réutilise le patron FastAPI du Chapitre 10 — vous êtes en terrain connu — et on lui donne la structure d'un service d'IA prêt à recevoir un modèle.

Partie 13 · Chapitre 25 · Axe IA

API IA avec Python + FastAPI + Docker

À la fin de ce chapitre, vous serez capable de…
  • structurer une API d'IA proprement, en séparant l'API et le « service d'IA » ;
  • conteneuriser cette API avec Docker ;
  • préparer un point d'entrée prêt à accueillir un vrai modèle (Chapitre 26) ;
  • tester votre service d'IA de bout en bout.

L'architecture visée

Une bonne API d'IA sépare nettement deux responsabilités : la couche API (recevoir les requêtes, valider, répondre) et le service d'IA (la logique qui « pense »). Cette séparation vous permettra, au chapitre suivant, de brancher un vrai modèle sans toucher à l'API.

flowchart LR
  C["Client"] -->|HTTP| A["FastAPI<br/>(couche API)"]
  A --> S["Service d'IA<br/>(logique / modèle)"]
  S --> A
  A -.->|conteneurisé| D[" Docker"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
La couche API et le service d'IA sont séparés : on pourra remplacer le « cerveau » sans réécrire l'API.

La structure du projet

ai-api/ ├── app/ │ ├── main.py # la couche API (FastAPI) │ └── ai_service.py # le service d'IA (le « cerveau ») ├── requirements.txt ├── .dockerignore └── Dockerfile

Le service d'IA (pour l'instant, un substitut)

On isole la logique d'IA dans un module dédié. Aujourd'hui, il renvoie une réponse factice ; au Chapitre 26, on remplacera uniquement l'intérieur de cette fonction par un appel à un vrai modèle — sans rien changer d'autre.

app/ai_service.py
# Le « cerveau » de l'application, isolé du reste.
# Au chapitre suivant, seul l'intérieur de generer() changera.

def generer(prompt: str) -> str:
    # TODO (Ch.26) : appeler un vrai modèle d'IA ici.
    # Substitut pédagogique pour l'instant :
    return f"Réponse simulée à : '{prompt}'"
app/main.py · la couche API
from fastapi import FastAPI
from pydantic import BaseModel
from app.ai_service import generer

app = FastAPI(title="API IA")

class Requete(BaseModel):
    prompt: str

@app.get("/sante")
def sante():
    return {"status": "ok"}

@app.post("/generer")
def generer_texte(req: Requete):
    reponse = generer(req.prompt)
    return {"prompt": req.prompt, "reponse": reponse}
requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6

Le Dockerfile

Identique au patron du Chapitre 10 — c'est tout l'intérêt d'avoir bien appris les bases :

Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./app ./app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Construire, lancer, tester

bash
docker build -t ai-api .
docker run -d -p 8000:8000 --name ia ai-api

curl -X POST http://localhost:8000/generer \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Bonjour"}'
# → {"prompt":"Bonjour","reponse":"Réponse simulée à : 'Bonjour'"}
Pourquoi passer par un substitut ?

En séparant l'API du modèle, vous avez une application qui tourne déjà, testable, avant même d'avoir un vrai modèle. C'est une excellente pratique : on valide la « plomberie » (API, conteneur, réseau) d'abord, puis on branche l'intelligence. Moins de choses cassent à la fois.

Si ça ne fonctionne pas

ImportError: cannot import name 'generer'

Cause probable : chemin d'import ou structure de dossiers incorrecte.
Vérification : le dossier app/ contient bien main.py et ai_service.py ; l'import est from app.ai_service import generer ; le CMD pointe sur app.main:app.

Pas de réponse malgré un conteneur actif

Cause quasi certaine : l'oubli de --host 0.0.0.0 (Chapitre 10). C'est le piège récurrent des API Python en conteneur.

Challenge

Ajoutez une validation : si le prompt est vide, renvoyez une erreur HTTP 400 explicite. Ajoutez aussi un champ longueur_max optionnel. Vous préparez ainsi une API robuste, prête à recevoir un vrai modèle au chapitre suivant.

En résumé
  • Une API d'IA se structure en deux couches : la couche API (FastAPI) et le service d'IA isolé.
  • Un substitut permet de valider toute la plomberie avant de brancher un vrai modèle.
  • Le Dockerfile est celui du Chapitre 10 : vos bases Docker se réutilisent telles quelles.
  • Au Chapitre 26, on ne changera que l'intérieur de generer().
14
Partie 14 · Axe IA

Intégration des API d'IA

Le moment où votre application devient réellement intelligente : on remplace le substitut du chapitre précédent par un appel à un vrai modèle de langage — proprement, sans dépendre d'un seul fournisseur, et sans jamais exposer sa clé.

Partie 14 · Chapitre 26 · Axe IA

Connecter une application Docker à une API d'IA

À la fin de ce chapitre, vous serez capable de…
  • appeler un modèle de langage depuis un conteneur ;
  • gérer la clé d'API en toute sécurité (variable d'environnement) ;
  • rester agnostique du fournisseur grâce à l'API compatible OpenAI ;
  • gérer les erreurs, les délais et les coûts.

Le principe d'une API de modèle

Un fournisseur d'IA expose un modèle de langage (LLM) derrière une API HTTP. Vous envoyez un message (le prompt), le service renvoie une réponse générée. Votre conteneur n'héberge pas le modèle : il l'appelle.

flowchart LR
  A["Votre application"] --> D[" Conteneur"]
  D -->|HTTPS + clé API| API[" API d'IA<br/>/v1/chat/completions"]
  API --> L[" LLM"]
  L --> API --> D --> A
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Votre conteneur envoie le prompt à l'API du modèle et reçoit la réponse. Le modèle vit chez le fournisseur.

Le standard qui vous rend agnostique : l'API compatible OpenAI

En 2026, la grande majorité des fournisseurs d'IA — et même les runtimes locaux comme Ollama ou Docker Model Runner (Chapitre 29) — exposent le même format d'API, dit « compatible OpenAI » : un point d'entrée /v1/chat/completions. Conséquence formidable : un seul code peut parler à n'importe lequel d'entre eux, en changeant juste deux variables — l'adresse (base_url) et la clé.

Pourquoi c'est décisif pour ce livre

On ne vous enferme dans aucun fournisseur. Le code ci-dessous fonctionne avec différents services d'IA et avec un modèle local, en modifiant une variable d'environnement. Si un fournisseur change ses tarifs ou son offre, vous basculez ailleurs sans réécrire l'application.

Brancher le vrai modèle

On reprend le projet ai-api du Chapitre 25 et on ne modifie que ai_service.py. On ajoute le client au requirements.txt :

requirements.txt · ajout
fastapi==0.115.0
uvicorn[standard]==0.30.6
openai==1.51.0          # client compatible avec toute API « OpenAI-compatible »
app/ai_service.py · la version réelle
import os
from openai import OpenAI, APIError, APITimeoutError

# Tout vient de l'environnement : rien n'est codé en dur.
client = OpenAI(
    base_url=os.environ["AI_BASE_URL"],   # ex. l'URL du fournisseur ou du modèle local
    api_key=os.environ["AI_API_KEY"],     # la clé, JAMAIS dans le code
)
MODELE = os.environ.get("AI_MODEL", "gpt-4o-mini")

def generer(prompt: str) -> str:
    try:
        reponse = client.chat.completions.create(
            model=MODELE,
            messages=[{"role": "user", "content": prompt}],
            timeout=30,
        )
        return reponse.choices[0].message.content
    except APITimeoutError:
        return " Le modèle a mis trop de temps à répondre. Réessayez."
    except APIError as e:
        # On journalise l'erreur réelle, on renvoie un message propre.
        print(f"Erreur API IA : {e}")
        return " Le service d'IA est momentanément indisponible."
Ce qui est bien fait ici

La couche API (main.py) n'a pas changé : la séparation du Chapitre 25 paie. La clé et l'URL viennent de l'environnement. Les erreurs et les délais sont gérés : l'application ne plante pas si le modèle est lent ou indisponible.

Passer la clé, sans jamais l'exposer

On fournit la configuration au démarrage, via un .env (Chapitre 14). Ce fichier reste dans .gitignore et .dockerignore.

.env · jamais versionné
AI_BASE_URL=https://api.mon-fournisseur.com/v1
AI_API_KEY=sk-votre-cle-secrete
AI_MODEL=gpt-4o-mini
bash · lancer le conteneur avec la config
docker run -d -p 8000:8000 --env-file .env --name ia ai-api

curl -X POST http://localhost:8000/generer \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Explique Docker en une phrase."}'
La faute à ne JAMAIS commettre

Ne collez jamais votre clé d'API dans le code, un Dockerfile, ou un dépôt Git. Une clé qui fuite peut être utilisée par n'importe qui — à vos frais. Les fournisseurs scrutent d'ailleurs les dépôts publics pour révoquer les clés exposées. Clé = variable d'environnement, point.

Requêtes, réponses, et le rôle des « messages »

Le format compatible OpenAI structure la conversation en messages ayant un rôle : system (les consignes de comportement), user (la demande), assistant (les réponses passées). Pour donner une personnalité ou des règles à votre IA :

exemple · ajouter un message « system »
messages=[
    {"role": "system", "content": "Tu es un assistant concis qui répond en français."},
    {"role": "user", "content": prompt},
]

La question des coûts

Une API d'IA distante est en général facturée à l'usage, selon la quantité de texte échangé (mesurée en tokens). Quelques réflexes :

  • Choisir un modèle adapté : les plus petits suffisent souvent et coûtent bien moins cher.
  • Limiter la longueur des réponses (paramètre max_tokens).
  • Éviter d'envoyer un contexte inutilement long à chaque requête.
  • Surveiller sa consommation, et poser des limites de dépenses côté fournisseur.
L'atout du mode agnostique

Comme votre code est agnostique, vous pouvez, pour le développement, pointer AI_BASE_URL vers un modèle local gratuit (Chapitre 29) et ne passer à une API payante qu'en production — sans changer une ligne de code. Économique et pratique.

Si ça ne fonctionne pas

401 Unauthorized / invalid api key

Cause : clé absente, erronée, ou non transmise au conteneur.
Vérification : le --env-file .env est-il présent ? docker exec ia env | grep AI_ montre-t-il les variables ?

Timeout ou lenteur

Cause : réseau, ou modèle très sollicité.
Solution : le timeout et la gestion d'erreur ci-dessus évitent que l'application se bloque ; affichez un message clair à l'utilisateur.

Challenge Pro

Ajoutez un message system configurable par variable d'environnement (AI_SYSTEM_PROMPT), et un paramètre max_tokens lu depuis l'environnement. Lancez deux conteneurs de la même image avec des personnalités différentes. Vous obtenez deux assistants distincts… à partir d'une seule image.

En résumé
  • Votre conteneur appelle un modèle via une API HTTP ; il ne l'héberge pas.
  • Le standard compatible OpenAI (/v1/chat/completions) rend le code agnostique : on change de fournisseur (ou on passe en local) via base_url et la clé.
  • La clé est toujours une variable d'environnement, jamais dans le code ou Git.
  • On gère erreurs et timeouts, et on surveille les coûts (tokens, taille du modèle, max_tokens).
15
Partie 15 · Axe IA

RAG et base vectorielle

Un modèle de langage ne connaît pas vos documents. Le RAG change cela : il permet à l'IA de répondre à partir de vos propres contenus. C'est la technique la plus utile de tout l'axe IA — et un excellent terrain pour orchestrer plusieurs conteneurs.

Partie 15 · Chapitre 27 · Axe IA

Comprendre le RAG

À la fin de ce chapitre, vous serez capable de…
  • expliquer le principe du RAG sans une seule formule mathématique ;
  • définir embeddings, base vectorielle, chunks, retrieval et contexte ;
  • décrire le trajet complet d'une question à une réponse fondée sur vos documents ;
  • identifier quels composants devront être conteneurisés.

Le problème : un modèle ne connaît pas vos documents

Un LLM a été entraîné sur des textes généraux ; il ignore le contenu de votre entreprise, de vos notes, de votre documentation interne. Lui poser une question sur vos documents donne, au mieux, une réponse vague, au pire une invention. Comment lui faire répondre à partir de votre savoir ?

L'idée du RAG en une phrase

RAG = Retrieval-Augmented Generation (génération augmentée par la recherche). Au lieu de demander au modèle de « savoir », on retrouve les passages pertinents de vos documents, on les lui fournit comme contexte, et on lui demande de répondre à partir de ce contexte. Le modèle ne récite pas : il synthétise ce qu'on vient de lui donner.

Visualisation : le trajet d'une question

flowchart TD
  Q[" Question de l'utilisateur"] --> R[" Recherche des passages pertinents<br/>dans vos documents"]
  R --> C[" Contexte<br/>(les meilleurs extraits)"]
  C --> P[" Question + Contexte<br/>assemblés dans un prompt"]
  P --> L[" LLM"]
  L --> A[" Réponse fondée sur VOS documents"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le RAG : on retrouve le bon contexte, on le donne au modèle, il répond à partir de lui.

Le vocabulaire, expliqué simplement

Les embeddings : transformer du sens en nombres

Un embedding est une liste de nombres qui représente le sens d'un texte. L'idée clé, sans mathématiques : deux textes qui parlent de la même chose obtiennent des embeddings proches. « Comment réinitialiser mon mot de passe ? » et « procédure de récupération d'accès » seront proches, même sans mots communs. C'est ce qui permet une recherche par le sens, et non par mots-clés exacts.

Analogie

Imaginez une immense bibliothèque où chaque texte est rangé selon son sujet, pas selon son titre. Les textes voisins traitent de thèmes voisins. Chercher, c'est aller au bon « rayon thématique » et prendre ce qui est autour. Les embeddings créent ce rangement par le sens.

Les chunks : découper pour mieux retrouver

On ne calcule pas un embedding sur un document entier de 50 pages : trop gros, trop imprécis. On le découpe en morceaux (chunks) de quelques paragraphes, et on calcule un embedding par chunk. Ainsi, la recherche retrouve le passage précis qui répond, pas tout le document.

La base vectorielle : ranger et retrouver par proximité

Une base vectorielle (vector database) stocke ces embeddings et sait répondre très vite à : « quels sont les chunks dont l'embedding est le plus proche de celui de la question ? ». C'est un moteur de recherche par similarité de sens. Exemples courants : Qdrant, Chroma, ou l'extension pgvector de PostgreSQL.

Le retrieval et le contexte

Le retrieval (la récupération), c'est l'étape de recherche : on transforme la question en embedding, on interroge la base vectorielle, on récupère les meilleurs chunks. Ces extraits forment le contexte que l'on ajoute au prompt envoyé au LLM.

Les deux temps du RAG

Un système RAG fonctionne en deux phases distinctes. Bien les séparer clarifie tout :

PhaseQuandCe qui se passe
Indexation (ingestion)Une fois, en amontDécouper les documents en chunks → calculer leurs embeddings → les stocker dans la base vectorielle.
Interrogation (query)À chaque questionEmbedding de la question → recherche des chunks proches → contexte + question → LLM → réponse.
Ce que le RAG n'est pas

Le RAG ne « ré-entraîne » pas le modèle. On ne touche pas au modèle : on lui fournit du contexte au moment de la question. C'est plus simple, moins cher, et vos documents peuvent changer à tout moment sans réentraînement.

Quels composants devront tourner en conteneurs ?

Regardez la liste : une base vectorielle, un backend qui orchestre indexation et interrogation, un accès à un modèle d'embeddings et à un LLM (via API ou local), et un frontend. Autant de services à conteneuriser et orchestrer avec Docker Compose — exactement le projet du chapitre suivant.

Exercices

Exercice 27.1 Intermédiaire · Pratique

Avant de construire le projet RAG complet (Chapitre 28), prenez en main la base vectorielle en la lançant seule et en vérifiant qu'elle répond.

bash · démarrer Qdrant en conteneur
docker run -d -p 6333:6333 --name qdrant qdrant/qdrant

# Vérifiez que son API répond
curl http://localhost:6333/collections
# → {"result":{"collections":[]}, ...}  (aucune collection pour l'instant)

À faire : ouvrez aussi http://localhost:6333/dashboard dans votre navigateur (interface web de Qdrant). Identifiez, parmi les concepts du chapitre, ce que cette base va stocker (les embeddings des chunks) et ce qu'elle saura faire (la recherche par similarité).

Voir la correction

Qdrant stockera des vecteurs (les embeddings de chaque chunk) accompagnés d'un payload (le texte du chunk). À l'interrogation, il retrouvera les vecteurs les plus proches de celui de la question — le retrieval par similarité. Une « collection » vide aujourd'hui se remplira à l'étape d'indexation du Chapitre 28. Nettoyez avec docker rm -f qdrant quand vous avez fini d'explorer.

Challenge de réflexion

Pour une FAQ d'entreprise de 200 pages, expliquez en quoi le RAG est supérieur à « coller les 200 pages dans le prompt à chaque question ». Pensez au coût (tokens), à la précision, et aux limites de taille de contexte. Vous comprendrez pourquoi le retrieval est indispensable.

En résumé
  • Le RAG retrouve les passages pertinents de vos documents et les donne au LLM comme contexte.
  • Les embeddings transforment le sens en nombres ; les textes proches de sens ont des embeddings proches.
  • On découpe en chunks, on stocke dans une base vectorielle, on retrouve par similarité.
  • Deux phases : indexation (une fois) et interrogation (à chaque question).
  • Le RAG ne réentraîne pas le modèle : il l'informe au bon moment.
Partie 15 · Chapitre 28 · Axe IA

Projet RAG Dockerisé

À la fin de ce chapitre, vous serez capable de…
  • construire un « chatbot documentaire » complet en RAG ;
  • orchestrer backend, base vectorielle et modèles avec Docker Compose ;
  • implémenter les deux phases : indexation et interrogation ;
  • réunir tout ce que vous avez appris, de Compose à l'IA.
Mini-projet — « Chatbot documentaire »

L'utilisateur fournit des documents (texte) ; il peut ensuite poser des questions et obtenir des réponses fondées sur ces documents. Une vraie application RAG, entièrement conteneurisée.

Architecture

flowchart TB
  U["Navigateur"] --> F[" frontend"]
  F -->|/index, /ask| B[" backend<br/>FastAPI (orchestre le RAG)"]
  B -->|embeddings + LLM| API[" API d'IA<br/>(compatible OpenAI)"]
  B -->|stocke / cherche vecteurs| Q[" base vectorielle<br/>Qdrant"]
  Q --> V[" volume qdrant"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le backend orchestre : il appelle l'API d'IA pour les embeddings et le LLM, et Qdrant pour stocker/rechercher.
Choix technologiques

Qdrant comme base vectorielle (image Docker officielle, simple). Les embeddings et le LLM passent par l'API compatible OpenAI du Chapitre 26 — donc agnostiques, et remplaçables par un modèle local (Chapitre 29). Alternative possible : pgvector pour réutiliser PostgreSQL au lieu de Qdrant.

Structure du projet

rag-chatbot/ ├── backend/ │ ├── main.py │ ├── requirements.txt │ └── Dockerfile ├── frontend/ │ ├── index.html │ └── Dockerfile ├── .env └── docker-compose.yml

Le backend : le cœur du RAG

backend/requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6
openai==1.51.0
qdrant-client==1.11.3
backend/main.py
import os, uuid
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct

app = FastAPI(title="RAG Chatbot")
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])

# --- Clients, tout depuis l'environnement ---
ai = OpenAI(base_url=os.environ["AI_BASE_URL"], api_key=os.environ["AI_API_KEY"])
EMBED_MODEL = os.environ.get("EMBED_MODEL", "text-embedding-3-small")
CHAT_MODEL  = os.environ.get("AI_MODEL", "gpt-4o-mini")
VECTOR_SIZE = int(os.environ.get("VECTOR_SIZE", "1536"))

qdrant = QdrantClient(url=os.environ["QDRANT_URL"])   # ex. http://qdrant:6333
COLLECTION = "documents"

@app.on_event("startup")
def setup():
    if not qdrant.collection_exists(COLLECTION):
        qdrant.create_collection(COLLECTION,
            vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE))

def embed(texte: str):
    return ai.embeddings.create(model=EMBED_MODEL, input=texte).data[0].embedding

def decouper(texte: str, taille: int = 500):
    # Découpage simple en chunks de ~500 caractères
    return [texte[i:i+taille] for i in range(0, len(texte), taille)]

class Doc(BaseModel):
    texte: str
class Question(BaseModel):
    question: str

# ---------- PHASE 1 : INDEXATION ----------
@app.post("/index")
def indexer(doc: Doc):
    points = []
    for chunk in decouper(doc.texte):
        points.append(PointStruct(id=str(uuid.uuid4()),
            vector=embed(chunk), payload={"texte": chunk}))
    qdrant.upsert(COLLECTION, points=points)
    return {"chunks_indexes": len(points)}

# ---------- PHASE 2 : INTERROGATION ----------
@app.post("/ask")
def demander(q: Question):
    # 1) retrouver les chunks proches de la question
    trouves = qdrant.search(COLLECTION, query_vector=embed(q.question), limit=3)
    contexte = "\n\n".join(p.payload["texte"] for p in trouves)
    # 2) construire le prompt avec le contexte
    prompt = (f"Réponds à la question en te basant UNIQUEMENT sur le contexte.\n\n"
              f"Contexte :\n{contexte}\n\nQuestion : {q.question}")
    # 3) demander au LLM
    r = ai.chat.completions.create(model=CHAT_MODEL,
        messages=[{"role": "user", "content": prompt}])
    return {"reponse": r.choices[0].message.content, "sources": len(trouves)}
Relisez les deux phases

/index = indexation (découper → embeddings → stocker). /ask = interrogation (embedding de la question → recherche → contexte → LLM). C'est exactement le schéma du Chapitre 27, en code.

backend/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Le frontend (minimal)

frontend/index.html · extrait
<textarea id="doc" placeholder="Collez un document…"></textarea>
<button onclick="indexer()">Indexer</button>
<input id="q" placeholder="Votre question">
<button onclick="demander()">Demander</button>
<pre id="rep"></pre>
<script>
  const API = "http://localhost:8000";
  async function indexer(){
    await fetch(API+"/index",{method:"POST",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({texte:doc.value})});
    alert("Document indexé !");
  }
  async function demander(){
    const r=await fetch(API+"/ask",{method:"POST",headers:{"Content-Type":"application/json"},
      body:JSON.stringify({question:q.value})});
    rep.textContent=(await r.json()).reponse;
  }
</script>
frontend/Dockerfile
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
EXPOSE 80

L'orchestration

.env
AI_BASE_URL=https://api.mon-fournisseur.com/v1
AI_API_KEY=sk-votre-cle
AI_MODEL=gpt-4o-mini
EMBED_MODEL=text-embedding-3-small
VECTOR_SIZE=1536
docker-compose.yml
services:
  frontend:
    build: ./frontend
    ports: ["8080:80"]
    depends_on: [backend]

  backend:
    build: ./backend
    ports: ["8000:8000"]
    environment:
      AI_BASE_URL: ${AI_BASE_URL}
      AI_API_KEY: ${AI_API_KEY}
      AI_MODEL: ${AI_MODEL}
      EMBED_MODEL: ${EMBED_MODEL}
      VECTOR_SIZE: ${VECTOR_SIZE}
      QDRANT_URL: http://qdrant:6333        # nom du service (Ch.12) !
    depends_on: [qdrant]

  qdrant:
    image: qdrant/qdrant:latest
    volumes:
      - qdrant:/qdrant/storage               # persistance des vecteurs (Ch.13)

volumes:
  qdrant:

Lancer, indexer, interroger

bash
docker compose up --build -d
# Ouvrez http://localhost:8080
# 1) collez un texte, cliquez « Indexer »
# 2) posez une question sur ce texte, cliquez « Demander »

Résultat attendu : la réponse est fondée sur le document que vous avez fourni — même si le modèle ne « connaissait » pas ce contenu. Testez la persistance : docker compose down puis up ; grâce au volume qdrant, vos documents restent indexés.

Erreurs possibles

  • Dimension d'embedding incohérenteVECTOR_SIZE doit correspondre à la taille des vecteurs du modèle d'embeddings choisi. Une erreur Qdrant sur la dimension pointe ici.
  • Le backend ne joint pas Qdrant → utilisez le nom de service qdrant, pas localhost (Chapitre 12).
  • 401 sur les embeddings/LLM → clé absente ou fournisseur qui ne propose pas d'endpoint d'embeddings (vérifiez son API).
  • Réponses hors-sujet → augmentez le nombre de chunks récupérés (limit) ou affinez la taille des chunks.
Challenge bonus Pro

Ajoutez le renvoi des extraits sources avec la réponse (les chunks utilisés), pour que l'utilisateur vérifie d'où vient l'information. Puis ajoutez la persistance des documents originaux dans PostgreSQL. Vous approchez de l'architecture du projet final (Partie 18).

En résumé
  • Un chatbot documentaire RAG réunit frontend, backend, base vectorielle et accès aux modèles.
  • Le backend implémente les deux phases : /index (indexation) et /ask (interrogation).
  • Qdrant stocke les vecteurs (avec volume) ; embeddings et LLM passent par l'API compatible OpenAI.
  • Docker Compose orchestre le tout : chaque service se joint par son nom, les données persistent par volume.
16
Partie 16 · Axe IA

IA locale et modèles open source

Jusqu'ici, vos modèles vivaient chez un fournisseur. On peut aussi les faire tourner chez soi : aucune donnée qui sort, aucun coût par requête. Et grâce à l'API compatible OpenAI, votre code n'aura presque pas à changer.

Partie 16 · Chapitre 29 · Axe IA

Exécuter un modèle IA localement

À la fin de ce chapitre, vous serez capable de…
  • comprendre l'inference, la mémoire et la quantization ;
  • faire tourner un modèle open source en conteneur avec Ollama ;
  • brancher votre application dessus sans réécrire de code ;
  • évaluer honnêtement les limites matérielles.

Le concept : faire « penser » un modèle sur votre machine

Faire tourner un modèle localement, c'est exécuter l'inference — le calcul par lequel un modèle produit une réponse — sur votre matériel plutôt que chez un fournisseur. Trois notions à connaître :

NotionCe qu'il faut retenir
InferenceL'exécution du modèle pour générer une réponse (par opposition à l'entraînement).
CPU vs GPUSur CPU, ça marche mais c'est lent. Un GPU accélère énormément (Chapitre 30).
MémoireUn modèle doit tenir en mémoire (RAM, ou VRAM du GPU). Plus il est gros, plus il en faut.
QuantizationUne « compression » du modèle (moins de précision par nombre) qui réduit fortement sa taille et ses besoins mémoire, avec une petite perte de qualité. C'est ce qui rend des modèles exécutables sur des machines modestes.
Soyons honnêtes sur le matériel

Un ordinateur classique ne peut pas faire tourner n'importe quel modèle. Les très gros modèles exigent beaucoup de mémoire et un GPU puissant. Mais de nombreux modèles open source quantizés et de taille modeste tournent très bien sur une machine récente, même sans GPU (plus lentement). Choisissez la taille du modèle selon votre matériel — ne promettez jamais l'impossible.

Deux outils pour l'IA locale en 2026

Ollama

L'outil le plus répandu pour exécuter des modèles open source localement. Il se lance en conteneur, télécharge les modèles à la demande, et — point crucial — expose une API compatible OpenAI.

Docker Model Runner

L'approche native Docker (intégrée à Docker Desktop) pour faire tourner des modèles comme des ressources Docker. Même esprit, même compatibilité d'API. Une alternative à surveiller.

Architecture

flowchart LR
  A["Votre application"] --> D[" Conteneur app"]
  D -->|API compatible OpenAI| O[" Conteneur Ollama<br/>(runtime d'inference)"]
  O --> M[" Modèle local<br/>(fichier quantizé)"]
  M --> V[" volume<br/>(modèles persistés)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Ollama tourne dans son conteneur et expose une API ; votre application l'appelle comme n'importe quelle API d'IA.

Ollama en conteneur, en pratique

bash · lancer Ollama et tirer un modèle
# Démarrer Ollama en conteneur, avec un volume pour garder les modèles
docker run -d -p 11434:11434 -v ollama:/root/.ollama --name ollama ollama/ollama

# Télécharger un modèle léger (exemple)
docker exec -it ollama ollama pull llama3.2
La magie de l'agnosticisme : zéro réécriture

Ollama expose /v1/chat/completions, exactement comme au Chapitre 26. Pour brancher votre application dessus, il suffit de changer deux variables d'environnement :

.env · pointer vers Ollama local au lieu d'un fournisseur
AI_BASE_URL=http://ollama:11434/v1   # le conteneur Ollama, par son nom
AI_API_KEY=ollama                    # clé factice : Ollama n'en exige pas
AI_MODEL=llama3.2

Votre API IA (Chapitre 25) et même votre chatbot RAG (Chapitre 28) fonctionnent maintenant 100 % en local, sans une ligne de code modifiée. C'est tout le bénéfice d'avoir codé agnostique.

Orchestrer app + Ollama avec Compose

docker-compose.yml · application + Ollama local
services:
  app:
    build: .
    ports: ["8000:8000"]
    environment:
      AI_BASE_URL: http://ollama:11434/v1
      AI_API_KEY: ollama
      AI_MODEL: llama3.2
    depends_on: [ollama]

  ollama:
    image: ollama/ollama
    volumes:
      - ollama:/root/.ollama          # modèles persistés (Ch.13)

volumes:
  ollama:
CPU par défaut, GPU en option

Tel quel, Ollama tourne sur le CPU : ça fonctionne partout, mais c'est plus lent sur les gros modèles. Pour exploiter un GPU NVIDIA et accélérer l'inference, il faut le NVIDIA Container Toolkit — c'est précisément l'objet du Chapitre 30.

API distante vs locale : que choisir ?

API distante Modèle local
CoûtPar requête (tokens)Gratuit à l'usage (mais matériel requis)
ConfidentialitéDonnées envoyées au fournisseurRien ne sort de votre machine
Qualité maxAccès aux plus gros modèlesLimitée par votre matériel
Mise en placeUne clé suffitTélécharger le modèle, gérer la mémoire
Le meilleur des deux mondes

Beaucoup développent en local (gratuit, privé) et déploient avec une API distante (plus puissante) — ou l'inverse pour des raisons de confidentialité. Grâce à votre code agnostique, ce choix se règle par une variable d'environnement, sans rien réécrire.

Challenge Pro

Reprenez votre chatbot RAG du Chapitre 28 et faites-le fonctionner entièrement en local : ajoutez un service ollama au Compose, tirez un modèle de chat et un modèle d'embeddings supportés par Ollama, et ajustez les variables. Vous obtiendrez un RAG privé, sans aucun appel externe.

En résumé
  • L'inference locale exécute le modèle sur votre matériel ; la quantization le rend accessible aux machines modestes.
  • Ollama (et Docker Model Runner) font tourner des modèles open source en conteneur, avec une API compatible OpenAI.
  • Brancher votre app en local = changer AI_BASE_URL et la clé. Aucune réécriture.
  • Soyez réaliste sur le matériel : choisissez la taille du modèle en conséquence.
17
Partie 17 · Axe IA

Docker + GPU

Pour accélérer l'IA locale, on exploite la carte graphique. Docker sait donner à un conteneur l'accès au GPU — moyennant quelques prérequis. Ce chapitre explique comment, avec prudence, et prévoit toujours une alternative CPU.

Partie 17 · Chapitre 30 · Axe IA

Comprendre Docker et GPU

À la fin de ce chapitre, vous serez capable de…
  • comprendre pourquoi le GPU accélère l'IA ;
  • connaître le rôle de CUDA et du NVIDIA Container Toolkit ;
  • donner à un conteneur l'accès au GPU avec --gpus ;
  • prévoir une alternative CPU pour continuer sans GPU.

Pourquoi un GPU pour l'IA ?

L'inference d'un modèle, c'est une avalanche de calculs matriciels. Un GPU (processeur graphique) est conçu pour faire un très grand nombre de ces calculs en parallèle — bien plus vite qu'un CPU. Résultat : des réponses en secondes plutôt qu'en dizaines de secondes. Pour l'IA locale, le GPU fait souvent la différence entre « utilisable » et « frustrant ».

Les pièces du puzzle

ÉlémentRôle
GPULe matériel qui exécute les calculs parallèles.
CUDALa technologie de NVIDIA qui permet aux logiciels d'utiliser le GPU pour du calcul (pas seulement l'affichage).
Pilote GPUInstallé sur la machine hôte, il permet au système de dialoguer avec le GPU.
NVIDIA Container ToolkitLe pont qui donne aux conteneurs Docker l'accès au GPU de l'hôte.
flowchart TD
  A[" Conteneur (app IA)"] --> B[" Docker + NVIDIA Container Toolkit"]
  B --> C[" Pilote GPU (hôte)"]
  C --> D[" GPU physique (CUDA)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le NVIDIA Container Toolkit relie le conteneur au GPU de l'hôte, via le pilote et CUDA.

Les prérequis, en toute honnêteté

Ce chapitre suppose un matériel précis

L'accès GPU décrit ici nécessite : un GPU NVIDIA compatible, ses pilotes installés sur l'hôte, et le NVIDIA Container Toolkit. Sans GPU NVIDIA, ces commandes ne fonctionneront pas — et c'est normal. Chaque exemple GPU de ce livre est accompagné d'une alternative CPU pour que vous puissiez continuer quoi qu'il arrive. Un GPU AMD ou une puce Apple Silicon ont d'autres approches, hors du périmètre de ce chapitre.

Donner le GPU à un conteneur

Une fois le NVIDIA Container Toolkit installé sur l'hôte, l'option --gpus expose le GPU au conteneur. Un test rapide :

bash · vérifier que le conteneur voit le GPU
docker run --gpus all --rm nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi

Si tout est en place, nvidia-smi affiche votre carte graphique depuis l'intérieur du conteneur : la preuve que le conteneur a bien accès au GPU.

Exemple concret : Ollama avec GPU

Reprenons Ollama du Chapitre 29 et donnons-lui le GPU pour accélérer l'inference.

bash · Ollama avec accès GPU
docker run -d --gpus all -p 11434:11434 -v ollama:/root/.ollama --name ollama ollama/ollama

En Compose, on déclare la réservation de GPU ainsi :

docker-compose.yml · réserver le GPU pour un service
services:
  ollama:
    image: ollama/ollama
    volumes:
      - ollama:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

volumes:
  ollama:

Toujours prévoir l'alternative CPU

C'est une règle d'or de ce livre : votre projet doit rester utilisable sans GPU. La bonne nouvelle, c'est qu'Ollama (et la plupart des runtimes) basculent automatiquement sur le CPU si aucun GPU n'est disponible. Concrètement :

  • Avec GPU : ajoutez --gpus all (ou la section deploy) → inference rapide.
  • Sans GPU : retirez simplement cette partie → même conteneur, inference sur CPU (plus lente, mais fonctionnelle).
Bonne pratique

Utilisez une variable ou deux fichiers Compose (un « GPU », un « CPU ») pour basculer sans effort. Ainsi, un collègue sans GPU peut lancer votre projet, et vous, avec GPU, profitez de l'accélération. Le même code, deux matériels.

Si ça ne fonctionne pas

« could not select device driver ... with capabilities: [[gpu]] »

Cause : le NVIDIA Container Toolkit n'est pas installé/configuré sur l'hôte.
Vérification : nvidia-smi fonctionne-t-il sur l'hôte ? Le toolkit est-il installé ?
Solution : installez pilotes + NVIDIA Container Toolkit (doc officielle), ou basculez sur l'alternative CPU en attendant.

« CUDA out of memory »

Cause : le modèle est trop gros pour la mémoire (VRAM) de votre GPU.
Solution : choisissez un modèle plus petit ou davantage quantizé (Chapitre 29), ou repassez sur CPU (plus de RAM disponible, mais plus lent).

Challenge

Si vous avez un GPU NVIDIA : mesurez la différence. Posez la même question à Ollama en CPU puis en GPU, et comparez le temps de réponse. Si vous n'avez pas de GPU : décrivez comment vous rendriez votre docker-compose.yml capable de fonctionner dans les deux cas, pour un collègue équipé et un autre non.

En résumé
  • Le GPU accélère l'inference grâce au calcul parallèle ; CUDA permet de l'exploiter.
  • Le NVIDIA Container Toolkit donne aux conteneurs l'accès au GPU de l'hôte ; on l'active avec --gpus all (ou deploy.resources en Compose).
  • Prérequis stricts : GPU NVIDIA + pilotes + toolkit. Sans cela, c'est normal que ça ne marche pas.
  • Prévoyez toujours une alternative CPU : le même conteneur doit rester utilisable sans GPU.
18
Partie 18

Projets de fin de formation

Le moment de vérité. Quatre projets à difficulté croissante, du plus simple au projet final qui réunit tout le livre. Ici, on ne vous tient plus la main de bout en bout : on vous donne le cahier des charges, l'architecture et les critères de réussite. À vous de construire.

Partie 18 · Mode d'emploi

Comment aborder ces projets

Chaque projet réutilise des chapitres précédents. Vous avez déjà écrit une grande partie du code : ces projets vous apprennent à assembler et à décider seul.

La bonne méthode

Pour chaque projet : lisez l'architecture, listez les services, puis construisez par étapes en testant à chaque fois (un service, puis deux, puis l'orchestration). Si vous bloquez, la méthode « Diagnostic en 5 minutes » (Chapitre 17) est votre meilleure alliée. Ne visez pas la perfection du premier coup : faites tourner, puis améliorez.


Projet 1 Débutant

« Mon premier environnement Docker »

Contexte & objectif

Une application minimale connectée à une base de données PostgreSQL. Objectif : maîtriser les briques fondamentales — un Dockerfile, un conteneur applicatif, un volume, un réseau, des variables — sans encore utiliser Compose, pour bien sentir chaque geste.

Architecture

flowchart LR
  A[" Application<br/>(API simple)"] -->|réseau Docker| D[" PostgreSQL"]
  D --> V[" Volume (données)"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Deux conteneurs, un réseau, un volume : le socle de tout projet Docker.

Cahier des charges

  • Une petite application (Node.js, Python ou autre) qui se connecte à PostgreSQL et lit/écrit une donnée.
  • Un Dockerfile propre pour l'application (base -slim, cache des dépendances, non-root).
  • PostgreSQL lancé avec un volume pour la persistance et ses variables (POSTGRES_*).
  • Les deux conteneurs sur un réseau commun, l'app joignant la base par son nom de conteneur.
  • La configuration (URL de base, identifiants) passée par variables d'environnement.

Étapes suggérées

  1. Créer le réseau : docker network create projet1-net.
  2. Lancer PostgreSQL sur ce réseau, avec volume et mot de passe (Chapitres 4, 13, 14).
  3. Écrire l'application et son Dockerfile (Chapitres 6, 7, 9 ou 10).
  4. Construire l'image, lancer l'app sur le réseau, connectée à la base par son nom (Chapitres 8, 12).

Critères de réussite (tests)

  • L'application démarre et lit/écrit dans PostgreSQL.
  • Après docker stop + docker start de la base, les données sont toujours là (volume OK).
  • Changer un identifiant se fait par variable, sans reconstruire l'image.
Challenge bonus

Traduisez ensuite ce projet en un docker-compose.yml (Chapitre 15). Vous mesurerez à quel point Compose simplifie tout ce que vous venez de faire à la main.


Projet 2 Intermédiaire

« Application Web complète »

Contexte & objectif

Une vraie application web à quatre services, orchestrée par Docker Compose. Objectif : maîtriser l'assemblage frontend + backend + base + cache, avec réseau automatique, volumes et healthchecks.

Architecture

flowchart TB
  U["Navigateur"] --> F[" Frontend"]
  F --> B[" Backend (API REST)"]
  B --> P[" PostgreSQL"]
  B --> R[" Redis (cache)"]
  P --> V[" volume"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Frontend + Backend + PostgreSQL + Redis, orchestrés par un seul docker-compose.yml.

Cahier des charges

  • Une application réaliste (ex. gestionnaire de tâches, mini-blog, liste de réservations) avec des opérations de lecture/écriture.
  • Backend REST connecté à PostgreSQL (persistance) et Redis (cache ou compteur).
  • Frontend qui consomme l'API.
  • Un unique docker-compose.yml avec réseau automatique, volume pour la base, variables via .env, et depends_on + healthcheck.
Vous avez déjà un point de départ

Le projet « Livre d'or » du Chapitre 16 est une base parfaite. Reprenez-le et enrichissez-le : ajoutez des fonctionnalités, une vraie page frontend, une logique de cache utile. L'enjeu est de vous approprier l'orchestration.

Critères de réussite (tests)

  • Un seul docker compose up démarre les 4 services.
  • Les données persistent après docker compose down puis up.
  • Le backend attend que la base soit saine (healthcheck) avant de démarrer.
  • Redis apporte une valeur réelle (cache accéléré, compteur, sessions…).
Challenge bonus

Ajoutez un reverse proxy (Chapitre 23) devant le frontend et le backend, pour tout servir sur un seul point d'entrée. Vous préparez le déploiement réel.


Projet 3 Avancé ·

« API IA Dockerisée »

Contexte & objectif

Une application qui génère du texte via un modèle d'IA, avec historique et gestion robuste des erreurs. Objectif : intégrer proprement une IA dans une architecture conteneurisée.

Architecture

flowchart LR
  U["Navigateur"] --> F[" Frontend"]
  F --> B[" Backend<br/>FastAPI"]
  B -->|génération| API["/ API d'IA<br/>(distante ou locale)"]
  B -->|historique| P[" PostgreSQL"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
Le backend appelle un modèle d'IA et conserve l'historique des échanges.

Cahier des charges

  • Un frontend où l'utilisateur saisit un prompt et voit la réponse générée.
  • Un backend FastAPI qui appelle un modèle via l'API compatible OpenAI (Chapitre 26), agnostique (distant ou local).
  • Historique des échanges stocké en PostgreSQL.
  • Gestion des erreurs et des timeouts (le modèle peut être lent ou indisponible).
  • Clé d'API et configuration par variables d'environnement ; logs lisibles.
Vos briques existantes

Partez de l'API IA du Chapitre 25/26 (couche API + service d'IA + appel réel), ajoutez la persistance PostgreSQL (Chapitres 8, 13) pour l'historique, et orchestrez avec Compose (Chapitre 16).

Critères de réussite (tests)

  • Une question renvoie une réponse générée par le modèle.
  • L'historique se retrouve après redémarrage (persistance).
  • Si le modèle est indisponible, l'application affiche un message clair au lieu de planter.
  • Basculer d'une API distante à un modèle local (Ollama) se fait par variables, sans changer le code.
Challenge bonus

Ajoutez un system prompt configurable et une limite de longueur (coûts). Puis faites tourner deux « personnalités » d'assistant à partir de la même image, via des variables différentes.


Projet final · le sommet du livre

« AI Knowledge Assistant »

Un assistant documentaire intelligent complet. L'utilisateur téléverse des documents ; l'assistant les indexe et répond à ses questions en s'appuyant dessus. Ce projet réunit l'intégralité de la formation — Docker, Compose, bases de données, RAG, base vectorielle et IA.

Objectif

Construire une application RAG de production miniature, entièrement conteneurisée, qui : reçoit des documents, en extrait le texte, calcule des embeddings, les stocke dans une base vectorielle, effectue une recherche vectorielle à chaque question, et génère une réponse fondée sur les documents — le tout avec une interface web, un backend, une base de données et une orchestration Compose.

Architecture

flowchart TB
  U["Navigateur"] --> F[" Frontend"]
  F -->|upload / questions| B[" Backend · FastAPI"]
  B --> P[" PostgreSQL<br/>(documents, historique)"]
  B --> Q[" Base vectorielle<br/>(Qdrant)"]
  B --> AI["/ API d'IA<br/>(embeddings + LLM)"]
  P --> VP[" volume"]
  Q --> VQ[" volume"]
  classDef default fill:#EAF2FE,stroke:#2079E8,color:#12213A,stroke-width:1.5px;
L'architecture finale : frontend, backend, base relationnelle, base vectorielle et modèles — orchestrés par Docker Compose.

Fonctionnalités attendues

FonctionnalitéCe que ça mobilise
Upload de documentsEndpoint d'ingestion (frontend + backend).
Extraction de texteLire le contenu des fichiers téléversés (texte, et idéalement PDF).
EmbeddingsDécoupage en chunks + API d'embeddings (Chapitre 27, 28).
Recherche vectorielleQdrant : stockage et recherche par similarité.
Génération de réponseContexte + question → LLM (Chapitre 26, 28).
Base de donnéesPostgreSQL : métadonnées des documents, historique des questions.
Interface webUpload, zone de question, affichage des réponses (et sources).
Docker ComposeOrchestration des 4+ services, réseaux, volumes, variables, healthchecks.

Plan de construction suggéré

  1. Repartez du chatbot RAG du Chapitre 28 (backend FastAPI + Qdrant + API d'IA). C'est votre socle.
  2. Ajoutez PostgreSQL (Chapitres 8, 13, 16) pour stocker les métadonnées des documents et l'historique des questions/réponses.
  3. Gérez l'upload de fichiers : un endpoint qui reçoit un fichier, en extrait le texte, puis lance l'indexation existante.
  4. Enrichissez le frontend : zone de téléversement, liste des documents indexés, zone de question, affichage de la réponse et des extraits sources.
  5. Orchestrez tout dans un docker-compose.yml : frontend, backend, PostgreSQL, Qdrant — avec volumes, .env, depends_on + healthchecks.
  6. Soignez la robustesse : erreurs d'IA gérées, logs clairs, images propres (multi-stage pour le frontend, non-root).
Restez agnostique et local si vous voulez

Grâce à l'API compatible OpenAI, vous pouvez développer gratuitement en local avec Ollama (Chapitre 29) pour les embeddings et le LLM, puis basculer sur une API distante en production — en changeant seulement des variables d'environnement. Idéal pour un projet personnel sans budget.

Critères de réussite

  • Un seul docker compose up --build démarre toute l'application.
  • L'utilisateur téléverse un document, puis obtient des réponses fondées sur ce document.
  • Les documents indexés et l'historique persistent après redémarrage (volumes).
  • Chaque service joint les autres par leur nom ; aucune clé n'est dans le code.
  • L'application ne plante pas si l'IA est momentanément indisponible.
Défis d'excellence Pro

Pour aller plus loin : afficher les extraits sources sous chaque réponse ; supporter les PDF (extraction de texte) ; ajouter une authentification simple ; écrire un pipeline CI/CD (Chapitre 22) qui construit et scanne les images ; préparer un déploiement avec reverse proxy et HTTPS (Chapitre 23). Chacun de ces défis est un vrai plus sur un portfolio.

Ce que ces projets prouvent
  • Vous savez assembler des services indépendants en une application cohérente.
  • Vous savez décider de l'architecture, pas seulement suivre un tutoriel.
  • Vous savez intégrer l'IA (API, RAG, local) dans du conteneurisé, proprement.
  • Le projet final réunit tout le livre : si vous le menez à bien, vous êtes autonome.
19
Partie 19

Autonomie professionnelle

Vous savez faire. Reste à savoir démarrer seul face à une page blanche, avec méthode. Cette partie vous donne deux outils que vous garderez toute votre vie de développeur : une démarche de démarrage, et une checklist professionnelle.

Partie 19 · Chapitre 31

Démarrer un projet Docker de zéro

À la fin de ce chapitre, vous serez capable de…
  • aborder n'importe quel nouveau projet avec une méthode claire ;
  • dérouler une checklist de démarrage en 12 étapes ;
  • éviter les erreurs de conception les plus coûteuses ;
  • construire dans le bon ordre, en testant à chaque palier.

La bonne posture : penser en services

Devant un nouveau projet, ne commencez pas par écrire du Dockerfile. Commencez par lister les services : de quoi l'application a-t-elle besoin pour tourner ? Un backend, une base, un cache, un frontend, un modèle d'IA ? Chaque brique deviendra un conteneur. Cette vue d'ensemble guide tout le reste.

La checklist de démarrage en 12 étapes

#ÉtapeCe que vous faites
1Identifier les servicesLister chaque brique (backend, base, cache, frontend, IA…).
2Choisir les images de baseOfficielles, versionnées, -slim par défaut (Ch.5, 7).
3Écrire les DockerfilesDépendances avant le code, non-root, multi-stage si utile (Ch.6, 7, 18).
4Configurer les réseauxServices sur un réseau commun, joints par leur nom (Ch.12).
5Configurer les volumesPersister ce qui doit survivre (bases, uploads) (Ch.13).
6Configurer les variablesConfig & secrets par l'environnement, jamais en dur (Ch.14).
7Créer le docker-compose.ymlAssembler services, réseaux, volumes, depends_on, healthchecks (Ch.15, 16).
8Testerdocker compose up, vérifier chaque service, puis l'ensemble.
9DéboguerMéthode « Diagnostic en 5 minutes » (Ch.17).
10OptimiserAlléger les images, exploiter le cache (Ch.19).
11SécuriserNon-root, secrets, scan, moindre privilège (Ch.20).
12Préparer le déploiementRegistry, CI/CD, reverse proxy, HTTPS (Ch.21, 22, 23).
Bonne pratique

Construisez par paliers : faites tourner un premier service seul, testez, ajoutez le deuxième, testez leur communication, et seulement ensuite orchestrez le tout. Un projet monté brique par brique se débogue infiniment mieux qu'un projet monté d'un bloc.

Attention aux excès

Ne sur-concevez pas. Toutes les applications n'ont pas besoin de Redis, d'un GPU ou d'un pipeline CI/CD complet dès le premier jour. Ajoutez chaque brique quand elle apporte une valeur réelle. « La meilleure architecture est la plus simple qui résout votre problème. »

Challenge

Prenez une idée d'application qui vous tient à cœur et déroulez les 12 étapes sur papier, sans écrire de code : quels services, quelles images, quels volumes, quelles variables ? Cet exercice de conception est ce que font les professionnels avant de taper la première ligne.

En résumé
  • On démarre en listant les services, pas en écrivant du Dockerfile.
  • La checklist en 12 étapes couvre tout : de la conception au déploiement.
  • On construit par paliers, en testant à chaque étape.
  • On reste simple : chaque brique doit se justifier.
Partie 19 · Chapitre 32

Checklist Docker professionnelle

À la fin de ce chapitre, vous serez capable de…
  • auditer n'importe quel projet Docker selon cinq axes ;
  • repérer ce qui manque avant une mise en production ;
  • disposer d'une référence à relire avant chaque livraison.

Gardez cette page sous la main : c'est le récapitulatif de tout le livre, condensé en points à vérifier. Avant de considérer un projet « prêt », passez-la en revue.

Développement

  • Un Dockerfile par service, dépendances avant le code.
  • Un .dockerignore à jour.
  • Un docker-compose.yml qui démarre tout d'une commande.
  • Volumes pour les données à persister.
  • Réseaux : services joints par leur nom.

Sécurité

  • Secrets par variables d'environnement, jamais dans l'image.
  • Utilisateur non-root (USER).
  • Images officielles, versionnées, à jour.
  • Surface d'attaque réduite (slim / multi-stage).
  • Images scannées (Docker Scout / Trivy).

Performance

  • Cache exploité (ordre des instructions).
  • Multi-stage builds là où c'est utile.
  • Images légères et mesurées (docker images).
  • Nettoyage dans la même couche que l'installation.

Production

  • Logs sur la sortie standard, consultables.
  • Healthchecks sur les services critiques.
  • Politique de redémarrage (restart: unless-stopped).
  • Configuration par environnement (dev / prod séparés).
  • Reverse proxy + HTTPS en façade.
  • Versions d'images précises, jamais latest.

IA (le cas échéant)

  • Clés d'API en variables d'environnement, code agnostique (compatible OpenAI).
  • Erreurs et timeouts des modèles gérés proprement.
  • Modèles / base vectorielle persistés par volumes.
  • Accès GPU configuré avec une alternative CPU.
  • Coûts maîtrisés (taille du modèle, max_tokens).
Comment l'utiliser

Avant chaque livraison, parcourez les cinq axes et cochez mentalement chaque point. Un point non coché n'interdit pas de livrer, mais il doit être un choix conscient, pas un oubli. C'est exactement ce que fait une revue de code professionnelle.

En résumé
  • Cinq axes d'audit : développement, sécurité, performance, production, IA.
  • Cette checklist condense tout le livre en points vérifiables.
  • Un point manquant doit être un choix, jamais un oubli.
20
Partie 20

Référence rapide

Les pages à revenir consulter toute votre vie de développeur : les commandes essentielles, les modèles de fichiers, la méthode de debug, et un glossaire. Puis une évaluation finale pour mesurer votre autonomie.

Partie 20 · Aide-mémoire

Cheat sheets

Commandes Docker essentielles

bash
# Conteneurs
docker run -d -p 8080:80 --name web nginx   # créer + démarrer (arrière-plan)
docker ps -a                                # lister (tous)
docker stop|start|restart web               # cycle de vie
docker rm -f web                            # supprimer (forcé)
docker logs -f web                          # suivre les logs
docker exec -it web sh                      # entrer dans le conteneur
docker inspect web                          # détails (JSON)

# Images
docker build -t mon-app .                   # construire
docker images                               # lister
docker rmi mon-app                          # supprimer
docker history mon-app                      # couches

# Registry
docker login
docker tag mon-app user/mon-app:1.0.0
docker push user/mon-app:1.0.0
docker pull user/mon-app:1.0.0

# Réseaux & volumes
docker network create net && docker network ls
docker volume create data && docker volume ls

# Nettoyage
docker system prune -a                      # tout ce qui est inutilisé (attention !)

Dockerfile cheat sheet

Dockerfile
FROM python:3.12-slim        # base légère, versionnée
WORKDIR /app                  # dossier de travail
COPY requirements.txt .       # dépendances D'ABORD (cache)
RUN pip install --no-cache-dir -r requirements.txt
COPY . .                      # code ENSUITE
ENV APP_ENV=production        # variable
RUN useradd -m appuser && true
USER appuser                  # non-root
EXPOSE 8000                   # documentation du port
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

# Multi-stage : construire dans une étape, ne garder que le résultat
FROM node:22-slim AS build
WORKDIR /app
COPY . . && RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html

Docker Compose cheat sheet

docker-compose.yml
services:
  api:
    build: .
    ports: ["8000:8000"]
    env_file: .env
    depends_on:
      db: { condition: service_healthy }
    restart: unless-stopped
  db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: secret }
    volumes: ["pgdata:/var/lib/postgresql/data"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5
volumes: { pgdata: {} }
bash · commandes Compose
docker compose up -d --build     # construire + démarrer (arrière-plan)
docker compose ps                # état des services
docker compose logs -f api       # logs d'un service
docker compose down              # arrêter + supprimer
docker compose down -v           # + supprimer les volumes (efface les données)

Debugging cheat sheet

bash · la méthode « Diagnostic en 5 minutes »
docker ps -a                     # 1. le conteneur tourne-t-il ?
docker logs <nom>                 # 2. que dit l'application ?  (n°1 !)
docker inspect <nom>              # 3. config, réseau, variables
# 4. ports  5. réseau  6. variables  7. volumes
docker exec -it <nom> sh          # déboguer de l'intérieur
SymptômePiste
port is already allocatedChanger le port hôte de -p.
Conteneur qui s'arrêtedocker logs ; un service doit rester en écoute.
connection refused (base)Nom de service + même réseau + healthcheck.
App inaccessible via -pÉcouter sur 0.0.0.0, pas 127.0.0.1.
Données perduesAjouter un volume sur le bon dossier.

Docker + IA cheat sheet

.env · rester agnostique (API distante ou modèle local)
# API distante
AI_BASE_URL=https://api.fournisseur.com/v1
AI_API_KEY=sk-...
AI_MODEL=gpt-4o-mini

# Modèle local (Ollama) : on change juste ces valeurs
AI_BASE_URL=http://ollama:11434/v1
AI_API_KEY=ollama
AI_MODEL=llama3.2
bash · IA locale & GPU
docker run -d -p 11434:11434 -v ollama:/root/.ollama --name ollama ollama/ollama
docker exec -it ollama ollama pull llama3.2
docker run --gpus all --rm nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi   # test GPU
python · appel de modèle (compatible OpenAI)
from openai import OpenAI
client = OpenAI(base_url=os.environ["AI_BASE_URL"], api_key=os.environ["AI_API_KEY"])
r = client.chat.completions.create(model=MODELE,
    messages=[{"role":"user","content":prompt}])
Partie 20 · Glossaire

Glossaire

Les termes clés du livre, définis simplement.

TermeDéfinition
Container (conteneur)Une instance en cours d'exécution d'une image : l'application isolée, avec tout son environnement, qui tourne.
ImageUn modèle figé, en lecture seule, contenant l'application et son environnement. On la lance pour obtenir un conteneur.
DockerfileLa recette texte qui décrit, étape par étape, comment construire une image.
RegistryLe serveur qui héberge et distribue les images (ex. Docker Hub).
VolumeUn stockage géré par Docker qui persiste les données au-delà de la vie d'un conteneur.
Network (réseau)Un « couloir privé » reliant des conteneurs, où chacun est joignable par son nom.
ComposeL'outil qui décrit et orchestre une application multi-conteneurs via un seul fichier YAML.
Layer (couche)Une strate d'une image, correspondant à une instruction du Dockerfile ; partagée et mise en cache.
BuildLa construction d'une image à partir d'un Dockerfile (docker build).
RuntimeLe moteur (Docker Engine) qui exécute les conteneurs ; par extension, la phase d'exécution.
APIUne interface par laquelle un logiciel en appelle un autre via des requêtes (ici, souvent en HTTP).
LLMLarge Language Model : un grand modèle de langage qui génère du texte.
EmbeddingUne liste de nombres représentant le sens d'un texte ; deux sens proches donnent des embeddings proches.
RAGRetrieval-Augmented Generation : retrouver des passages pertinents et les fournir au modèle comme contexte.
Base vectorielleUne base de données qui stocke des embeddings et recherche par similarité de sens.
InferenceL'exécution d'un modèle pour produire une réponse (par opposition à l'entraînement).
GPUProcesseur graphique, capable de nombreux calculs en parallèle ; accélère fortement l'inference.
Partie 20 · Évaluation

Évaluation finale

Le test de vérité. Répondez sans regarder le livre ; les corrections sont repliées. Si un domaine vous met en difficulté, relisez le chapitre correspondant — c'est le but.

1. Théorie

  1. Quelle est la différence entre une image et un conteneur ?
  2. Pourquoi un conteneur est-il plus léger qu'une machine virtuelle ?
  3. À quoi sert un volume, et que se passe-t-il sans lui ?
  4. Pourquoi ne met-on jamais de mot de passe dans un Dockerfile ?
Voir la correction

1. L'image est un modèle figé ; le conteneur est une instance en exécution de cette image. 2. Il partage le noyau de l'hôte au lieu d'embarquer un OS invité complet. 3. Le volume persiste les données au-delà du conteneur ; sans lui, supprimer/recréer le conteneur efface les données. 4. L'image est inspectable (docker history) et distribuée : un secret y serait exposé ; on le fournit par variable d'environnement au démarrage.

2. Commandes à écrire

  1. Lancer nginx en arrière-plan, accessible sur le port 9090 de votre machine.
  2. Suivre en temps réel les logs d'un conteneur nommé api.
  3. Construire une image nommée monsite:1.0 depuis le dossier courant.
  4. Entrer dans un shell du conteneur db.
Voir la correction
docker run -d -p 9090:80 nginx
docker logs -f api
docker build -t monsite:1.0 .
docker exec -it db sh

3. Déboguer

Votre backend, dans un projet Compose, renvoie sans cesse connection refused vers la base db. Décrivez votre démarche et les trois causes les plus probables.

Voir la correction

Démarche : docker compose logs (backend + db), puis vérifier réseau et config. Trois causes probables : (1) la base n'était pas prête au démarrage → ajouter depends_on: condition: service_healthy + healthcheck ; (2) mauvais hôte (localhost au lieu de db) ; (3) identifiants/URL erronés dans les variables.

4. Compléter un Dockerfile

Complétez ce Dockerfile pour une API Python qui écoute sur le port 8000, en respectant les bonnes pratiques (cache, non-root) :

FROM python:3.12-slim
WORKDIR /app
# … à compléter …
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Voir la correction
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser
USER appuser
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

5. Corriger un docker-compose.yml

Ce fichier a un problème qui empêchera l'API de trouver sa base au démarrage. Lequel, et comment le corriger ?

services:
  api:
    build: .
    environment:
      DATABASE_URL: postgresql://user:pass@localhost:5432/app
  db:
    image: postgres:16
    environment: { POSTGRES_PASSWORD: pass }
Voir la correction

L'API vise localhost — c'est-à-dire son propre conteneur — au lieu du service db. Corriger l'hôte : ...@db:5432/app. Bonus : ajouter un healthcheck sur db et depends_on: {db: {condition: service_healthy}} sur l'API pour éviter la course au démarrage.

6. Problèmes IA, réseau et volume

  1. IA : votre appel de modèle renvoie 401 Unauthorized dans le conteneur. Deux vérifications ?
  2. Réseau : deux conteneurs ne se voient pas. Que vérifiez-vous en premier ?
  3. Volume : vos données PostgreSQL disparaissent à chaque docker compose down. Pourquoi, et comment l'éviter ?
Voir la correction

IA : la clé est-elle bien passée au conteneur (--env-file / environment) ? La valeur est-elle correcte (docker exec ... env | grep AI_) ? Réseau : sont-ils sur le même réseau, et s'appellent-ils par leur nom de service ? Volume : soit aucun volume n'est monté, soit vous utilisez down -v qui supprime les volumes ; montez un volume nommé sur /var/lib/postgresql/data et utilisez down sans -v.

7. Concevoir une architecture

Sur papier, concevez l'architecture d'un « assistant documentaire » : listez les services, ce que chacun fait, comment ils communiquent, ce qui doit persister, et où passent les secrets. (Pas de solution unique — comparez avec le Projet final de la Partie 18.)

Projet d'autonomie (sans solution)

Choisissez une application qui vous est utile (suivi de dépenses, bloc-notes intelligent, assistant de veille…) et construisez-la de A à Z, conteneurisée, avec au moins une base de données et une fonctionnalité d'IA. Personne ne vous donnera la correction — et c'est précisément la preuve que vous êtes devenu autonome. C'est le vrai diplôme de cette formation.

Conclusion

Vous êtes arrivé au bout

Souvenez-vous du premier docker run hello-world. Vous étiez peut-être un débutant complet. Aujourd'hui, vous concevez des architectures conteneurisées intégrant de l'intelligence artificielle. Ce chemin, vous l'avez parcouru avec vos mains.

Vous avez traversé toute la trajectoire promise en ouverture :

Débutant complet Utilisateur Docker Développeur Docker Docker professionnel Développeur d'applications IA Dockerisées

La dernière page : pouvez-vous répondre « oui » ?

La formation est réussie si vous répondez « oui » à ces questions. Sinon, le chapitre indiqué vous attend.

?Est-ce que je comprends ce qu'est un conteneur et une image ? (Ch.2)
?Est-ce que je peux écrire un Dockerfile et construire une image ? (Ch.6-7)
?Est-ce que je sais lancer et gérer des conteneurs ? (Ch.4)
?Est-ce que je sais utiliser Docker Compose et connecter plusieurs services ? (Ch.15-16)
?Est-ce que je sais utiliser PostgreSQL, des volumes et des variables d'environnement ? (Ch.8, 13, 14)
?Est-ce que je sais diagnostiquer un conteneur qui ne fonctionne pas ? (Ch.17)
?Est-ce que je sais optimiser une image et appliquer les bonnes pratiques de sécurité ? (Ch.19-20)
?Est-ce que je peux conteneuriser un backend et un frontend ? (Ch.8-11)
?Est-ce que je comprends Docker dans un workflow CI/CD et un déploiement ? (Ch.22-23)
?Est-ce que je sais connecter une application Docker à une API d'IA ? (Ch.26)
?Est-ce que je comprends les bases du RAG et sais construire une application IA Dockerisée ? (Ch.27-28)
?Est-ce que je comprends l'IA locale et le rôle du GPU ? (Ch.29-30)
?Est-ce que je peux commencer seul un nouveau projet Docker ? (Ch.31-32)
Un dernier mot

La conteneurisation et l'IA évoluent vite, mais les fondations que vous avez acquises — isolation, reproductibilité, orchestration, code agnostique — resteront valables longtemps. Continuez à construire, à casser, à réparer. C'est ainsi qu'on progresse. Bonne route.

Kelenix Tech — Docker + IA

Fin du livre · De Zéro à Professionnel · Édition 2026

Docker + IA · De Zéro à Professionnel — une formation Kelenix Tech.

Édition 2026 · Compose V2 · livre complet — 33 chapitres, 4 projets, cheat sheets & glossaire.