infra / docker
Dockerfile : les bases
Explication
Ce que vous allez apprendre
- Écrire un Dockerfile minimal en comprenant le rôle de chaque instruction (
FROM,WORKDIR,COPY,RUN,CMD) - Comprendre le principe des couches et pourquoi il influence la taille et la vitesse de build
- Distinguer clairement
ENTRYPOINT(binaire fixe) deCMD(arguments par défaut remplaçables) - Construire une image avec
docker build -tet la lancer avec les bons ports - Protéger une image avec un
.dockerignorepour éviter d'y embarquer des fichiers sensibles ou inutiles
Dans quel contexte ?
Une développeuse doit packager pour la première fois son API FastAPI, jusque-là seulement lancée en local avec uvicorn. Elle doit décrire, dans un Dockerfile, l'image de base Python à utiliser, où copier son code, comment installer les dépendances, et quelle commande lancer au démarrage — pour qu'un collègue, ou un serveur de production, puisse reproduire exactement le même environnement sans jamais avoir eu à l'installer manuellement.
De l'image toute faite à sa propre image
Jusqu'ici, on a téléchargé et lancé des images déjà construites par d'autres (nginx, ubuntu...). Mais pour faire tourner sa propre application, il faut pouvoir construire sa propre image. C'est exactement à ça que sert un Dockerfile : un fichier texte qui décrit, étape par étape, comment fabriquer une image — un peu comme une recette de cuisine décrit comment obtenir un plat à partir d'ingrédients.
| Instruction | Rôle |
|---|---|
FROM | Image de base sur laquelle construire |
WORKDIR | Répertoire de travail dans l'image |
COPY | Copie des fichiers du projet vers l'image |
RUN | Exécute une commande au moment du build |
CMD | Commande lancée par défaut au démarrage du conteneur |
Prérequis
Cette leçon suppose que tu es à l'aise avec les commandes de base (docker run, docker ps) vues dans les deux premières leçons.
Le principe des couches
Chaque instruction d'un Dockerfile (FROM, COPY, RUN...) crée une nouvelle couche (layer) empilée sur les précédentes. C'est une idée centrale à bien comprendre dès maintenant, car elle influence tout le reste : la vitesse de construction, la taille finale de l'image, et l'usage du cache (approfondi dans une leçon ultérieure). Chaque couche est en pratique un ensemble de différences par rapport à la couche du dessous, un peu comme des calques superposés dans un logiciel de dessin.
Ce que fait chaque instruction, en clair
FROMchoisit le point de départ : on ne repart jamais de zéro, mais d'une image existante (ici une version minimale de Python).WORKDIRfixe le dossier de travail à l'intérieur de l'image, pour éviter de répéter des chemins absolus partout.COPYfait entrer des fichiers du projet dans l'image.RUNexécute une commande pendant la construction (par exemple installer des dépendances) — le résultat est figé dans l'image.CMDne s'exécute pas au moment du build : c'est la commande lancée par défaut quand un conteneur démarre.
ENTRYPOINT vs CMD, le piège classique
Beaucoup de débutants confondent les deux. Retenir l'image du "binaire fixe" : ENTRYPOINT définit le programme qui sera toujours exécuté, CMD fournit ses arguments par défaut, que l'on peut librement remplacer au lancement sans toucher au Dockerfile.
Ne pas oublier .dockerignore
Sans lui, COPY . . embarque aussi les fichiers .git, les dépendances déjà installées ou même des secrets locaux dans l'image.
Piège fréquent
Oublier un .dockerignore est une erreur de sécurité fréquente : un fichier .env contenant des mots de passe, copié par mégarde via COPY . ., se retrouve gravé dans l'image et reste consultable par quiconque y a accès, même après l'avoir supprimé du code source.
Commandes & code
Dockerfile : les bases
# Dockerfile minimal pour une API Python/FastAPI
FROM python:3.12-slim
# FROM définit l'image de base : toujours choisir la plus légère qui couvre le besoin
WORKDIR /app
# WORKDIR crée (si besoin) et fixe le répertoire de travail pour toutes les instructions suivantes
COPY requirements.txt .
# COPY copie des fichiers depuis le contexte de build vers l'image
RUN pip install --no-cache-dir -r requirements.txt
# RUN exécute une commande AU MOMENT DU BUILD, crée une nouvelle couche (layer)
COPY . .
# Copie le reste du code APRÈS l'installation des dépendances (voir leçon cache pour le pourquoi)
ENV PORT=8000
# ENV définit une variable d'environnement disponible au build ET à l'exécution
EXPOSE 8000
# EXPOSE documente le port utilisé (n'ouvre rien tout seul, -p reste nécessaire au run)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
# CMD définit la commande par défaut au démarrage du conteneur (forme exec, recommandée)docker build -t mon-api:1.0 . # construit l'image, "." = contexte de build (dossier courant)
docker build -t mon-api:1.0 -f Dockerfile.prod . # utilise un Dockerfile au nom custom
docker run -d -p 8000:8000 mon-api:1.0 # lance l'image construite# CMD vs ENTRYPOINT
# CMD seul : commande par défaut, ENTIÈREMENT remplaçable par un argument de "docker run"
# ENTRYPOINT seul: commande fixe, les arguments de "docker run" lui sont ajoutés en argument
# Les deux combinés : ENTRYPOINT = binaire fixe, CMD = arguments par défaut (remplaçables)
ENTRYPOINT ["uvicorn", "app.main:app"]
CMD ["--host", "0.0.0.0", "--port", "8000"]
# docker run mon-api --port 9000 -> remplace uniquement CMD, garde uvicorn app.main:app# .dockerignore : évite de copier des fichiers inutiles/sensibles dans le contexte de build
.git
__pycache__/
*.pyc
.env
node_modules/
tests/Résumé
FROM,WORKDIR,COPY,RUN,ENV,EXPOSE,CMDforment le socle d'un Dockerfile.ENTRYPOINTfixe le binaire exécuté,CMDfournit ses arguments par défaut — remplaçables audocker run..dockerignoreévite de copier.git,node_modules, secrets et fichiers de test dans l'image finale.
Exercices pratiques
Mission : corriger une image qui fuit des secrets et un port mal exposé
Objectif : Corriger un Dockerfile bâclé pour éviter une fuite de secrets et bien distinguer ENTRYPOINT de CMD.
Contexte
Un stagiaire a écrit un Dockerfile FastAPI sans .dockerignore : COPY . . a embarqué le fichier .env contenant un mot de passe, visible dans l'image publiée. Il a aussi mélangé ENTRYPOINT et CMD, rendant impossible de changer le port sans modifier le Dockerfile. Tu dois corriger les deux problèmes.