3.1 Apprendre a créer un Dockerfile (Python - FastApi)
Création d'un Dockerfile pour une application Python FastAPI.
Bien, maintenant que la théorie est derrière nous, on passe aux choses sérieuses : la création d'un Dockerfile pas à pas.
Pour cet exemple, on va conteneuriser une petite application en Python.
Bref, l'objectif de cette leçon est d'apprendre à écrire un Dockerfile de manière efficiente, avec les bonnes pratiques générales de l'industrie.
Le projet de départ
Pour qu'on puisse travailler ensemble, je vous ai préparé un petit projet basé sur FastAPI. Vous pouvez récupérer le code source ici : 👉 Dépôt GitHub : courses-docker-fastapi
Si on regarde à l'intérieur de ce dépôt, voici ce qu'on y trouve :
main.py: C'est le cœur de notre application, le fichier qui appelle le framework FastAPI pour lancer le serveur.requirements.txt: Le fichier indispensable en Python. Il liste toutes les dépendances nécessaires pour faire tourner le projet (il a été généré très classiquement grâce à la commandepip freeze > requirements.txt).README.md: Le fichier d'explications qui vous donne la marche à suivre pour installer correctement le projet en local, que vous soyez sur Windows, Linux ou macOS.
GET /). C'est vraiment le truc le plus basique qu'il puisse y avoir, mais c'est exactement ce qu'il nous faut : une application simple pour pouvoir se concentrer à 100 % sur l'écriture de notre Dockerfile !Étape 1 : L'analyse avant l'action
La première étape pour créer un Dockerfile, c'est l'analyse (une méthode un peu passive, certes). Ça peut paraître bête, mais analyser le code de prime abord permet de comprendre dans quel bordel... hum, dans quel langage et quel framework on va devoir mettre les mains. Et c'est crucial, car chaque technologie a ses propres spécificités.
Dans notre cas, on observe du Python avec le framework FastAPI. De manière générale, pour que notre application Python puisse tourner, il va nous falloir un serveur.
Pourquoi ? Quelle est la différence ? Parce que Python est un langage interprété et non compilé (contrairement au Go, C, C++, Rust... par exemple). Notre machine hôte ne sait pas lire du Python nativement, il va donc lui falloir un interpréteur, qui agira comme un serveur pour traiter les requêtes.
Dans l'écosystème Python, il existe plusieurs serveurs (Uvicorn, Gunicorn...) et différentes façons d'aborder le traitement des requêtes. Dans notre cas, nous utilisons FastAPI, qui est un framework à vocation asynchrone.
Mais au fait, que veut dire asynchrone ? En gros, le programme n'attend pas qu'une tâche soit finie pour en commencer une autre.
L'approche Synchrone
Sur ce schéma, on voit bien que le serveur reçoit 4 tâches (A, B, C, D) et va les traiter strictement dans l'ordre d'arrivée. Le serveur synchrone exécute les tâches de manière linéaire et standard : il fait A, puis quand A est terminé, il passe à B, etc.

L'approche Asynchrone
Lui, il exécute les requêtes de manière beaucoup plus pragmatique. Les mêmes tâches arrivent (A, B, C, D), mais elles ne ressortent pas forcément dans l'ordre. En résumé : imaginons que la tâche A prenne environ 3 secondes à se réaliser et la tâche B seulement 1 seconde. Le serveur asynchrone permettra à la tâche B d'être traitée et terminée avant la tâche A, sans la bloquer !

L'approche asynchrone est un peut le nouveau standard depuis quelques années.
Petit lexique des serveurs Python (liste non exhaustive) :
En serveurs Synchrones (souvent appelés WSGI) :
- Gunicorn
- Flask (son serveur de développement par défaut)
- Waitress
En serveurs Asynchrones (souvent appelés ASGI) :
- Uvicorn
- Hypercorn
- Daphne
Pour conclure cette analyse : FastAPI étant par définition asynchrone, il est tout naturel d'utiliser un serveur asynchrone pour en tirer les meilleures performances. Pour notre projet, on partira donc sur Uvicorn (default FastApi)!
Interstice : Tester l'application en local
Avant de dockeriser tout ça, c'est toujours une bonne idée de vérifier que l'application fonctionne sur votre propre machine (le fameux "ça marche chez moi !").
Si vous n'avez pas encore Python sur votre ordinateur, il faudra d'abord l'installer. Cela vous donnera notamment accès à la commande pip, qui est le gestionnaire de paquets de l'écosystème Python (l'équivalent de npm en NodeJS).
Ensuite, pour lancer le projet, il vous suffit de lire le fichier README.md du dépôt, ou de simplement installer les dépendances requises via le fichier requirements.txt.
Petite subtilité technique
Pour dire à pip de lire à l'intérieur d'un fichier, il ne faut pas oublier le flag -r (pour read). La commande correcte est donc :
pip install -r requirements.txt
Une fois que tous les paquets sont téléchargés et installés, vous pourrez lancer le serveur de développement de FastAPI pour tester l'application.
Tapez cette commande dans votre terminal :
fastapi dev main.py
uvicorn main:app --reload fera exactement le même job !)Et voilà ! Votre serveur tourne localement. Vous pouvez ouvrir votre navigateur sur l'adresse indiquée (généralement http://localhost:8000) pour admirer votre magnifique "Hello World".
Étape 1 : Le .dockerignore (Le videur de la boîte)
Maintenant que l'on a bien étudié notre application, trouvé quel langage et quel framework utiliser, et compris comment tout ça s'articule, un petit conseil avant de foncer : lisez la documentation !
C'est tellement important, il ne faut vraiment pas lésiner là-dessus. Même si l'IA est géniale pour vous aider au quotidien, n'hésitez pas à lire les docs officielles. On en tire beaucoup de choses que l'IA ne recrache pas forcément, et surtout des astuces parfaitement à jour.
Bien, la toute première étape avant même de créer notre Dockerfile, c'est de créer un fichier .dockerignore. Ce petit fichier a l'air inoffensif, mais il va changer énormément de choses pour la suite. C'est ultra important.
Pourquoi c'est indispensable ?
Par la suite, vous comprendrez que nous n'avons pas besoin de copier toute notre machine dans notre conteneur. Copier des trucs inutiles, c'est rendre l'image trop lourde, inutilement lente à construire, et surtout, ça peut créer des failles de sécurité. En effet, moins il y a de fichiers et de paquets inutiles embarqués, moins la surface d'attaque est grande pour un éventuel pirate.
Dans notre démarche, on va donc commencer par créer un .dockerignore. Il va nous permettre d'empêcher les dossiers parasites de polluer notre futur conteneur. Le résultat ? Un conteneur plus léger, plus rapide et plus sécurisé.
Création du fichier
Pour ce faire, nous allons créer un fichier .dockerignore à la racine de notre projet. Vous pouvez le faire avec votre éditeur préféré (VSCode, PhpStorm...) ou simplement via votre terminal avec la commande :
touch .dockerignore
Vous devriez voir ceci dans votre arborescence de fichiers :

Dans ce dossier, on remarque quelques éléments qui n'ont rien à faire dans notre image finale :
- Le dossier de cache de Python (le fameux
__pycache__). - Le dossier
.venvqui contient tous les paquets Python téléchargés pour faire fonctionner l'application sur votre machine (on les réinstallera proprement de zéro à l'intérieur du conteneur !). - Le
.gitignorequi sert à éviter de polluer Git. - Le
README.mdqui explique le projet aux humains.
Remplir le .dockerignore
Maintenant que le fichier est créé, on va l'ouvrir et lui lister tout ce qu'on ne veut absolument pas transférer dans notre futur conteneur. Copiez-collez ceci à l'intérieur :
# Les fichiers de cache Python
__pycache__
*.pyc
# L'environnement virtuel local
.venv
# Les fichiers liés à Git
.gitignore
.git
# La documentation
README.md
Et voilà ! Avec ça, vous vous assurez de partir sur une base ultra saine.
Étape 2 : Le fameux Dockerfile
Maintenant, passons à l'étape la plus cruciale : le fameux Dockerfile. C'est lui qui va nous permettre de décrire scrupuleusement toutes les étapes pour packager notre application.
Comme tout à l'heure, on va créer ce fichier à la racine de notre projet, soit avec votre éditeur de texte préféré, soit directement dans le terminal avec :
touch Dockerfile
Rassembler les pièces du puzzle
Avant d'écrire la moindre ligne, nous devons récapituler comment fonctionne notre application. Nous avons déjà des indices clés à portée de main :
- Le langage : Python (donc un langage interprété).
- Le framework : FastAPI (donc asynchrone par définition).
- Le serveur : Uvicorn (par défaut, adapté à l'asynchrone).
On sait également qu'on a des fichiers indispensables pour que notre application fonctionne factuellement :
requirements.txt: pour installer toutes les dépendances Python.main.py: le cœur de notre code.
On va donc partir d'un constat simple. En se basant sur ces éléments factuels, sur la manière dont on lance notre serveur en local, et sur nos connaissances de base en réseau et système Linux, on sait déjà plusieurs choses.
Voici la logique que notre fichier va devoir suivre, étape par étape :
- On va utiliser du Python (il nous faut un environnement avec Python préinstallé).
- On aura besoin de récupérer nos fichiers locaux (
main.pyetrequirements.txt) pour les mettre dans le conteneur. - On va devoir installer les dépendances (via la commande
pip install). - On devra lancer notre serveur applicatif (notre fameuse commande Uvicorn/FastAPI).
Le Dockerfile : une simple traduction
Dans cette petite liste, vous avez déjà, dans les grandes lignes, le modèle exact de votre futur Dockerfile.
Cette logique va simplement être transposée dans une syntaxe compréhensible et utilisable par Docker de manière efficiente. Bien sûr, pour de la vraie production, avec de l'expérience, on ira encore beaucoup plus loin dans l'optimisation et la sécurité. Mais avec ça, vous avez déjà une base propre, saine et fonctionnelle.
En fait, le Dockerfile n'a rien de sorcier en soi. Il s'agit simplement de transposer une suite logique d'actions humaines en lignes interprétées par Docker.
Transposition de la logique vers le code
Maintenant, le tout est de transposer notre logique vers du code que Docker pourra comprendre. Pour ça, il existe une pléthore d'outils (que l'on appelle des instructions) qui permettent de faire cela au sein du Dockerfile.
Chaque mot désigne une action précise qui va être demandée à Docker pour atteindre le résultat escompté de manière factuelle. On va les énumérer ensemble :
FROM: Définit l'image de base de notre conteneur (ou de l'étape en cours, dans le cas d'un build multi-étapes). C'est presque toujours la première ligne d'unDockerfile.WORKDIR: Définit le répertoire de travail. C'est le dossier dans lequel on va se placer à l'intérieur du conteneur pour exécuter toutes les instructions suivantes.COPY: Permet de copier des fichiers d'un point A (notre ordinateur local) vers un point B (l'intérieur de notre conteneur).RUN: Permet de lancer une commande (comme une installation de paquet) pendant la phase de construction de l'image.CMD: Définit la commande par défaut qui s'exécutera au lancement du conteneur (pour démarrer notre serveur, par exemple).ENV: Définit des variables d'environnement qui resteront actives pendant toute la durée de vie du conteneur.ARG: Le cousin éphémère deENV. C'est une variable qui n'est utilisable que pendant la création de l'image, puis elle disparaît.EXPOSE: Permet d'indiquer (d'exposer) les ports réseau sur lesquels l'application du conteneur va écouter.USER: Permet de forcer l'utilisation d'un utilisateur spécifique, plutôt que le compte administrateurrootutilisé par défaut (indispensable pour la sécurité).VOLUME: Définit un chemin où les données du conteneur seront stockées de manière persistante pour ne pas être effacées à l'arrêt de celui-ci.ADD: Le grand frère deCOPY. Il copie des fichiers, mais peut en plus télécharger des fichiers depuis une URL et décompresser automatiquement les archives (.tar,.gz,.zip).ENTRYPOINT: Similaire àCMD, mais beaucoup plus strict. Il empêche de remplacer facilement la commande principale quand on lance le conteneur avec des arguments.
Création de notre Dockerfile
Pour ce faire, nous allons utiliser notre logique courante, couplée à la connaissance des mots-clés Docker que l'on vient de voir, pour lui donner ses instructions de manière factuelle.
Pour trouver une image de base, rendez-vous sur le catalogue officiel : Docker Hub. Ce site va nous permettre de trouver facilement les images dont nous allons avoir besoin, avec la documentation et quelques explications en prime.
Pour cet essai, on va partir sur une image Python classique (basée sur Debian ou Ubuntu).
Voici nos étapes pour packager l'application rapidement et simplement :
- Prendre une image Python.
- Créer un espace de travail prévu pour notre code (généralement un répertoire
/app). - Copier nos fichiers.
- Installer les dépendances.
- Lancer notre serveur.
Le Dockerfile en pratique
Voici comment transposer cette logique dans notre fichier Dockerfile :
# 1. Image Python récente de base
FROM python:3.12
# 2. Choix du répertoire de travail dans le conteneur
WORKDIR /app
# 3. Copie du fichier des dépendances dans le conteneur
COPY requirements.txt .
# 4. Installation des dépendances Python nécessaires
RUN pip install --no-cache-dir -r requirements.txt
# 5. Copie de notre code applicatif
COPY main.py .
# 6. Commande pour lancer le serveur Python
ENTRYPOINT ["fastapi", "dev", "main.py", "--host", "0.0.0.0"]
La subtilité réseau (Le fameux piège du 0.0.0.0)
Il y a une petite subtilité cruciale pour lancer le serveur. En local sur votre machine, vous tapez simplement fastapi dev. Mais dans le conteneur, on ajoute l'argument --host 0.0.0.0. Pourquoi ?
C'est purement du réseau. Un conteneur agit comme un vrai PC isolé à l'intérieur du vôtre.
- Pour lui, l'IP
127.0.0.1(oulocalhost) lui est strictement réservée. Si le serveur écoute surlocalhost, il n'acceptera de parler qu'à lui-même. Depuis votre machine hôte, vous ne pourrez pas y accéder ! - En revanche, forcer l'écoute sur l'IP
0.0.0.0indique au serveur : "Accepte les connexions extérieures provenant de n'importe où (y compris depuis ma vraie machine)".
Lancer la machine !
Pour donner vie à ce Dockerfile, il vous suffit d'utiliser ces deux commandes dans votre terminal :
1. Construire l'image (le Build) :
docker build -t fastapi-dev .
fastapi-dev (et lui assignera le tag par défaut latest). N'oubliez pas le point . à la fin, il indique à Docker de chercher le Dockerfile dans le dossier courant !2. Lancer le conteneur (le Run) :
docker run -d -p 8000:8000 fastapi-dev:latest
-d), on mappe le port 8000 de notre PC vers le port 8000 du conteneur (-p), et on lui précise quelle image utiliser.Rendez-vous maintenant sur http://127.0.0.1:8000 ou http://localhost:8000 dans votre navigateur, et vous verrez votre magnifique application en ligne !