L'API est excellente — et prisonnière de votre machine. On la scelle dans une boîte standard qui tourne à l'identique partout : le système devient portable, en une commande. The API is excellent — and trapped on your machine. We seal it in a standard box that runs identically everywhere: the system becomes portable, with one command.
Bloc 3 : le cerveau a une adresse, POST /predict répond en millisecondes. Mais tout vit sur votre laptop — votre Python, vos libs, vos réglages. Copiez-le ailleurs : crash. Le démon « ça marche chez moi » est de retour.
Block 3: the brain has an address, POST /predict answers in milliseconds. But everything lives on your laptop — your Python, your libs, your settings. Copy it elsewhere: crash. The "works on my machine" demon is back.
Regardez : le système se scelle dans un conteneur. Code, Python, libs, mini-OS — tout l'empilement voyage ensemble, en couches réutilisables. Une image, mille conteneurs rigoureusement identiques. Watch: the system seals itself into a container. Code, Python, libs, mini-OS — the whole stack travels together, in reusable layers. One image, a thousand rigorously identical containers.
La même image démarre sur le laptop, le serveur, le cloud. Mais qui se souvient de quel modèle est dans la boîte ? Entraîné quand, avec quelle PR-AUC ? Personne. C'est exactement le point de départ du bloc 5. The same image starts on the laptop, the server, the cloud. But who remembers which model is inside the box? Trained when, with what PR-AUC? Nobody. That's exactly where block 5 picks up.
1956 : Malcom McLean regarde des dockers vider son camion caisse par caisse — et invente la boîte en acier standardisée. Même taille partout, empilable, scellée : le contenu peut être n'importe quoi, la boîte est toujours la même. Docker, 2013 : la même idée, pour le logiciel. 1956: Malcom McLean watches dockers unload his truck crate by crate — and invents the standardized steel box. Same size everywhere, stackable, sealed: the content can be anything, the box is always the same. Docker, 2013: the same idea, for software.
La VM, c'est l'immeuble entier : un OS complet par application — des Go, des minutes. Le conteneur, c'est la chambre : le noyau de l'hôte est partagé — des Mo, des millisecondes, des dizaines par machine. C'est cette légèreté qui rendra Kubernetes possible au bloc 7. A VM is the whole building: a full OS per application — GBs, minutes. A container is the room: the host's kernel is shared — MBs, milliseconds, dozens per machine. That lightness is what will make Kubernetes possible in block 7.
Le Dockerfile est la recette exécutable de votre environnement : FROM python:3.11-slim, COPY requirements.txt, RUN pip install, COPY src/, CMD uvicorn. Versionné dans Git, il ne ment jamais — contrairement au README. Trois optimisations font la différence entre un build amateur et un build de production :
The Dockerfile is your environment's executable recipe: FROM python:3.11-slim, COPY requirements.txt, RUN pip install, COPY src/, CMD uvicorn. Versioned in Git, it never lies — unlike the README. Three optimizations separate an amateur build from a production one:
Le système n'est plus un programme : c'est un organisme — l'API, la base de transactions, et MLflow qui arrive au bloc 5. Docker Compose décrit l'état voulu dans un YAML commité, et docker compose up le fait exister.
The system is no longer a program: it's an organism — the API, the transactions database, and MLflow arriving in block 5. Docker Compose describes the desired state in a committed YAML, and docker compose up makes it exist.
db:5432, MLflow à mlflow:5000 — par leur nom de service. Piège n°1 : « localhost » dans un conteneur, c'est le conteneur lui-même.Compose creates a private network: the API reaches the database at db:5432, MLflow at mlflow:5000 — by their service name. Trap #1: "localhost" inside a container is the container itself.down puis up, les données sont toujours là.The container is disposable, the transactions are not. A volume is a Docker-managed disk plugged into the box: down then up, the data is still there.docker logs d'abord (la boîte noire), docker exec ensuite (entrer vérifier). Puis on durcit : utilisateur non-root — le stagiaire n'a pas les clés du camion — et des tags épinglés, jamais :latest en prod.docker logs first (the black box), docker exec next (step in and check). Then harden: non-root user — the intern doesn't get the truck keys — and pinned tags, never :latest in prod.L'image décrit, le conteneur exécute ; l'ordre des couches fait le cache ; la boîte reste légère et propre ; logs puis exec quand ça casse ; jamais root, jamais :latest. La boîte est une frontière de sécurité autant qu'une promesse de reproductibilité. The image describes, the container runs; layer order makes the cache; keep the box light and clean; logs then exec when it breaks; never root, never :latest. The box is a security boundary as much as a promise of reproducibility.
« Le système démarre à l'identique sur n'importe quelle machine — en une commande. » "The system starts identically on any machine — with one command."
git clone, docker compose up, café. Le doc d'installation de 40 pages est mort — et le démon « ça marche chez moi » avec lui. git clone, docker compose up, coffee. The 40-page install doc is dead — and the "works on my machine" demon with it.
Trois missions, en binômes — un qui code, un qui questionne, on échange à la mi-temps. Les durées viennent du plan de cours : Three missions, in pairs — one codes, one questions, swap at half-time. Durations come straight from the course plan:
Une image < 500 Mo dont le /predict répond depuis l'extérieur, un docker compose up qui lève les trois services et des transactions qui survivent au down, trois pannes expliquées par écrit, et un Dockerfile durci (non-root, tags épinglés) commité. C'est cette boîte que la CI construira toute seule au bloc 6.
An image < 500 MB whose /predict answers from outside, a docker compose up that raises all three services with transactions surviving a down, three failures explained in writing, and a hardened Dockerfile (non-root, pinned tags) committed. This is the box CI will build on its own in block 6.
Ce guide reprend les trois missions du lab, commande par commande. Prérequis : le bloc 3 terminé — dans fraud-detection/, l'API src/serve/app.py répond en local, avec models/model.pkl et models/scaler.pkl à bord. À la fin : une image optimisée en multi-stage qui sert /predict depuis la boîte, un docker compose up qui lève API + PostgreSQL + MLflow, et les réflexes de debug quand ça casse.
This guide replays the lab's three missions, command by command. Prerequisite: block 3 finished — inside fraud-detection/, the src/serve/app.py API answers locally, with models/model.pkl and models/scaler.pkl on board. At the end: a multi-stage optimized image serving /predict from the box, a docker compose up that raises API + PostgreSQL + MLflow, and the debugging reflexes for when it breaks.
macOS et Windows : installez Docker Desktop depuis docker.com (Windows : activez WSL 2 quand l'installeur le propose), lancez l'application, puis vérifiez dans un terminal. Ubuntu/Debian : le dépôt officiel, puis le groupe docker pour travailler sans sudo. macOS and Windows: install Docker Desktop from docker.com (Windows: enable WSL 2 when the installer offers it), launch the app, then verify in a terminal. Ubuntu/Debian: the official repository, then the docker group to work without sudo.
# macOS / Windows: install Docker Desktop, start it, then:
docker --version
# Ubuntu / Debian:
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" \
| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# post-install (Linux): use docker without sudo
sudo usermod -aG docker $USER
newgrp docker
docker run hello-world
À la racine de fraud-detection/ : la liste exacte de ce que la boîte doit contenir. C'est cette couche qui sera mise en cache — elle change rarement, elle passe en premier dans le Dockerfile.
At the root of fraud-detection/: the exact list of what the box must contain. This is the layer that gets cached — it rarely changes, so it goes first in the Dockerfile.
fastapi
uvicorn[standard]
pydantic
scikit-learn
joblib
numpy
pandas
Fichier Dockerfile, à la racine. Notez l'ordre : requirements avant le code, pour que la couche pip reste en cache. Et dès la v1, la boîte n'est pas root : un utilisateur dédié, qui possède data/ pour que /feedback puisse écrire.
File Dockerfile, at the root. Note the order: requirements before the code, so the pip layer stays cached. And from v1 on, the box is not root: a dedicated user, who owns data/ so /feedback can write.
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY src/ src/
COPY models/ models/
RUN adduser --disabled-password --gecos "" apiuser \
&& mkdir -p /app/data \
&& chown -R apiuser /app/data
USER apiuser
EXPOSE 8000
CMD ["uvicorn", "src.serve.app:app", "--host", "0.0.0.0", "--port", "8000"]
Fichier .dockerignore, à la racine lui aussi. Sans lui, COPY embarque les CSV de data/, le venv et le .git dans le contexte de build — des centaines de Mo inutiles, et parfois des secrets.
File .dockerignore, at the root as well. Without it, COPY drags data/'s CSVs, the venv and the .git into the build context — hundreds of useless MB, and sometimes secrets.
data/
.venv/
.git/
notebooks/
__pycache__/
*.pyc
.pytest_cache/
mlruns/
Premier build : chaque instruction crée une couche. Relancez la commande juste après : tout sort du cache en quelques secondes. Notez la taille — c'est la référence à battre. First build: each instruction creates a layer. Rerun the command right after: everything comes from the cache in seconds. Note the size — that is the number to beat.
cd fraud-detection
docker build -t fraud-api:0.1 .
docker images fraud-api
Arrêtez d'abord l'uvicorn local du bloc 3 s'il tourne encore (il occupe le port 8000). Puis lancez le conteneur en arrière-plan et interrogez-le exactement comme au bloc 3 — même API, mais servie depuis la boîte. First stop block 3's local uvicorn if it is still running (it holds port 8000). Then start the container in the background and query it exactly like in block 3 — same API, now served from the box.
docker run -d -p 8000:8000 --name fraud-api fraud-api:0.1
curl -s http://127.0.0.1:8000/health
curl -s -X POST http://127.0.0.1:8000/predict \
-H "Content-Type: application/json" \
-d '{"time": 3600, "amount": 129.90, "v14": -8.31, "v12": -5.20}'
Remplacez le Dockerfile : un étage atelier qui installe les dépendances dans /install, un étage final propre qui ne reçoit que le résultat. On livre la boîte, pas l'atelier. Replace the Dockerfile: a workshop stage installing the dependencies into /install, a clean final stage that only receives the result. We ship the box, not the workshop.
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY src/ src/
COPY models/ models/
RUN adduser --disabled-password --gecos "" apiuser \
&& mkdir -p /app/data \
&& chown -R apiuser /app/data
USER apiuser
EXPOSE 8000
CMD ["uvicorn", "src.serve.app:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t fraud-api:0.2 .
docker images fraud-api
Fichier docker-compose.yml, à la racine. Trois organes : l'API construite depuis votre Dockerfile, PostgreSQL avec un volume pour que les transactions survivent, MLflow (qui arrive au bloc 5) avec sa base SQLite sur volume. Les services se joignent par leur nom : db:5432, mlflow:5000 — jamais localhost.
File docker-compose.yml, at the root. Three organs: the API built from your Dockerfile, PostgreSQL with a volume so the transactions survive, MLflow (arriving in block 5) with its SQLite store on a volume. Services reach each other by name: db:5432, mlflow:5000 — never localhost.
services:
api:
build: .
image: fraud-api:0.2
ports:
- "8000:8000"
depends_on:
- db
- mlflow
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: dotika
POSTGRES_PASSWORD: dotika
POSTGRES_DB: transactions
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
mlflow:
image: ghcr.io/mlflow/mlflow:v2.14.1
command: mlflow server --host 0.0.0.0 --backend-store-uri sqlite:////mlflow/mlflow.db
ports:
- "5000:5000"
volumes:
- mlflowdata:/mlflow
volumes:
pgdata:
mlflowdata:
Retirez le conteneur solo de la mission 1 (il occupe le port 8000), puis une seule commande lève l'organisme entier. Vérifiez ensuite les trois URLs : l'API, MLflow dans le navigateur (http://localhost:5000), et le pouls de la base. Remove mission 1's solo container (it holds port 8000), then a single command raises the whole organism. Then check the three URLs: the API, MLflow in the browser (http://localhost:5000), and the database's pulse.
docker rm -f fraud-api
docker compose up -d
docker compose ps
curl -s http://127.0.0.1:8000/health
curl -s -o /dev/null -w "MLflow: HTTP %{http_code}\n" http://127.0.0.1:5000
docker compose exec db pg_isready -U dotika
Premier geste de tout diagnostic : relire ce que le processus a dit. Le flag -f suit le flux en direct (Ctrl+C pour quitter, le conteneur continue de tourner). The first move of any diagnosis: reread what the process said. The -f flag follows the stream live (Ctrl+C to quit; the container keeps running).
docker compose logs api --tail 20
docker compose logs -f api
Quand les logs ne suffisent pas, on entre dans la boîte : un shell dans le conteneur, pour vérifier que les fichiers attendus sont bien là et sous quelle identité tourne le processus. When the logs are not enough, step into the box: a shell inside the container, to check that the expected files are there and which identity the process runs as.
docker compose exec api sh
ls models/
whoami
python -c "import sklearn; print(sklearn.__version__)"
exit
Un tag versionné dit exactement ce qui tourne — :latest peut changer entre deux déploiements. Épinglez votre image comme les images de base le sont déjà (python:3.11-slim, postgres:16-alpine, mlflow:v2.14.1). A versioned tag says exactly what is running — :latest can change between two deployments. Pin your image just like the base images already are (python:3.11-slim, postgres:16-alpine, mlflow:v2.14.1).
docker tag fraud-api:0.2 fraud-api:1.0.0
docker images fraud-api
docker compose ps
permission denied … /var/run/docker.sock — sous Linux, vous n'êtes pas dans le groupe docker : sudo usermod -aG docker $USER, puis déconnexion/reconnexion (ou newgrp docker). Bind for 0.0.0.0:8000 failed: port is already allocated — un ancien conteneur ou l'uvicorn local du bloc 3 tient le port : docker ps puis docker rm -f fraud-api, ou lsof -ti :8000 pour trouver le processus local. "transferring context" interminable — le .dockerignore manque, est mal orthographié, ou n'est pas à côté du Dockerfile : sans lui, data/ et .venv/ partent dans le contexte. pip très lent sur Mac Apple Silicon — vous construisez une image amd64 émulée : laissez Docker construire en natif arm64 (le défaut) ou passez --platform explicitement, python:3.11-slim existe pour les deux architectures. permission denied … /var/run/docker.sock — on Linux, you are not in the docker group: sudo usermod -aG docker $USER, then log out and back in (or newgrp docker). Bind for 0.0.0.0:8000 failed: port is already allocated — an old container or block 3's local uvicorn holds the port: docker ps then docker rm -f fraud-api, or lsof -ti :8000 to find the local process. Endless "transferring context" — the .dockerignore is missing, misspelled, or not next to the Dockerfile: without it, data/ and .venv/ ship into the context. pip very slow on Apple Silicon Macs — you are building an emulated amd64 image: let Docker build native arm64 (the default) or pass --platform explicitly; python:3.11-slim exists for both architectures.