Introduction
Dans cet atelier, nous allons approfondir les techniques liées au Dockerfile en explorant des concepts avancés qui vous aideront à créer des images Docker plus légères, sécurisées et flexibles. Nous aborderons en détail les instructions Dockerfile, les constructions multi-étapes (multi-stage builds) et l'utilité des fichiers .dockerignore. Nous étudierons également le concept crucial de couches (layers) dans les images Docker. À l'issue de cet atelier, vous aurez une compréhension complète de ces méthodes avancées et saurez comment les appliquer à vos propres projets.
Ce laboratoire est conçu pour les débutants, offrant des explications détaillées et anticipant les points de confusion courants. Nous utiliserons l'IDE Web (basé sur VS Code) pour toutes les tâches d'édition, ce qui facilite la création et la modification de fichiers directement depuis votre navigateur.
Comprendre les Instructions Dockerfile et les Couches
Commençons par créer un Dockerfile utilisant diverses instructions. Nous allons construire une image pour une application web Python utilisant Flask, et nous verrons comment chaque instruction contribue aux couches de notre image Docker.
- Tout d'abord, créons un nouveau répertoire pour notre projet. Dans le terminal de l'IDE Web, exécutez :
mkdir -p ~/project/advanced-dockerfile && cd ~/project/advanced-dockerfile
Cette commande crée un dossier nommé advanced-dockerfile à l'intérieur du dossier project, puis se déplace dedans.
Créons maintenant notre fichier d'application. Dans l'explorateur de fichiers de l'IDE (généralement à gauche), faites un clic droit sur le dossier
advanced-dockerfileet sélectionnez "New File". Nommez ce fichierapp.py.Ouvrez
app.pyet ajoutez le code Python suivant :
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f"Hello from {os.environ.get('ENVIRONMENT', 'unknown')} environment!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Il s'agit d'une application Flask simple qui renvoie un message de bienvenue incluant le nom de l'environnement dans lequel elle s'exécute.
- Ensuite, nous devons créer un fichier
requirements.txtpour spécifier nos dépendances Python. Créez un nouveau fichier nommérequirements.txtdans le même répertoire et ajoutez le contenu suivant :
Flask==2.0.1
Werkzeug==2.0.1
Nous spécifions ici des versions exactes pour Flask et Werkzeug afin de garantir la compatibilité.
- Créons maintenant notre Dockerfile. Créez un nouveau fichier nommé
Dockerfile(avec un 'D' majuscule) dans le même répertoire et ajoutez le contenu suivant :
## Use an official Python runtime as the base image
FROM python:3.9-slim
## Set the working directory in the container
WORKDIR /app
## Set an environment variable
ENV ENVIRONMENT=production
## Copy the requirements file into the container
COPY requirements.txt .
## Install the required packages
RUN pip install --no-cache-dir -r requirements.txt
## Copy the application code into the container
COPY app.py .
## Specify the command to run when the container starts
CMD ["python", "app.py"]
## Expose the port the app runs on
EXPOSE 5000
## Add labels for metadata
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo for advanced Dockerfile techniques"
Analysons ces instructions pour comprendre comment elles génèrent les couches de notre image :
FROM python:3.9-slim: C'est toujours la première instruction. Elle définit l'image de base. Cela crée la première couche de notre image, incluant l'environnement d'exécution Python.WORKDIR /app: Définit le répertoire de travail pour les instructions suivantes. Cela ne crée pas de nouvelle couche de données, mais modifie le comportement des instructions suivantes.ENV ENVIRONMENT=production: Définit une variable d'environnement. Les variables d'environnement ne créent pas de couches de fichiers, mais sont stockées dans les métadonnées de l'image.COPY requirements.txt .: Copie le fichier des dépendances depuis notre machine hôte vers l'image. Cela crée une nouvelle couche contenant uniquement ce fichier.RUN pip install --no-cache-dir -r requirements.txt: Exécute une commande dans le conteneur pendant la construction. Cela installe nos dépendances Python et crée une nouvelle couche contenant tous les paquets installés.COPY app.py .: Copie le code de notre application dans l'image, créant une autre couche.CMD ["python", "app.py"]: Spécifie la commande à exécuter au démarrage du conteneur. Cela ne crée pas de couche, mais définit la commande par défaut.EXPOSE 5000: Il s'agit en réalité d'une forme de documentation. Cela indique à Docker que le conteneur écoutera sur ce port, mais ne publie pas réellement le port. Aucune couche n'est créée.LABEL ...: Ajoute des métadonnées à l'image. Comme l'instruction ENV, les labels ne créent pas de couches mais sont stockés dans les métadonnées.
Chaque instruction RUN, COPY et ADD dans un Dockerfile crée une nouvelle couche. Les couches sont un concept fondamental de Docker permettant un stockage et un transfert efficaces des images. Lorsque vous modifiez votre Dockerfile et reconstruisez l'image, Docker réutilise les couches en cache qui n'ont pas changé, accélérant ainsi le processus.
- Maintenant que nous comprenons le rôle de notre Dockerfile, construisons l'image. Dans le terminal, exécutez :
docker build -t advanced-flask-app .
Cette commande construit une nouvelle image Docker taguée advanced-flask-app. Le . à la fin indique à Docker de chercher le Dockerfile dans le répertoire courant.
Vous verrez défiler les étapes du processus de construction. Notez comment chaque étape correspond à une instruction et comment Docker mentionne "Using cache" pour les étapes inchangées si vous relancez la commande.
- Une fois la construction terminée, nous pouvons lancer un conteneur basé sur notre nouvelle image :
docker run -d -p 5000:5000 --name flask-container advanced-flask-app
Explication des options :
-dlance le conteneur en mode détaché (en arrière-plan).-p 5000:5000mappe le port 5000 de votre hôte sur le port 5000 du conteneur.--name flask-containerdonne un nom personnalisé à notre conteneur.advanced-flask-appest le nom de l'image utilisée.
Vérifiez que le conteneur est bien actif :
docker ps
- Pour tester si l'application fonctionne, utilisez la commande
curl:
curl http://localhost:5000
Vous devriez voir le message : "Hello from production environment!"
Si vous préférez, vous pouvez aussi ouvrir un nouvel onglet dans votre navigateur et accéder à http://localhost:5000.
En cas de problème, consultez les journaux du conteneur :
docker logs flask-container
Cela affichera les messages d'erreur ou les sorties de votre application Flask.
Constructions Multi-étapes (Multi-stage Builds)
Maintenant que nous maîtrisons les bases, explorons une technique plus avancée : les builds multi-étapes. Cette méthode permet d'utiliser plusieurs instructions FROM dans un seul Dockerfile. C'est particulièrement utile pour réduire la taille de l'image finale en ne copiant que les fichiers nécessaires d'une étape à l'autre.
Modifions notre Dockerfile pour utiliser cette technique :
- Dans l'IDE Web, ouvrez le
Dockerfilecréé précédemment. - Remplacez tout son contenu par celui-ci :
## Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
## Final stage
FROM python:3.9-slim
WORKDIR /app
## Copy only the installed packages from the builder stage
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
ENV ENVIRONMENT=production
CMD ["python", "app.py"]
EXPOSE 5000
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="1.0"
LABEL description="Flask app demo with multi-stage build"
Analysons ce qui se passe ici :
Nous commençons par une étape nommée
builder:- Nous utilisons l'image Python 3.9-slim pour rester léger.
- Nous installons les dépendances avec
pip install --user. Cela installe les paquets dans le répertoire personnel de l'utilisateur.
Ensuite, nous définissons l'étape finale :
- Nous repartons d'une image Python 3.9-slim toute neuve.
- Nous copions uniquement les paquets installés depuis l'étape
builder, spécifiquement depuis/root/.local. - Nous copions notre code source.
- Nous ajoutons le répertoire des binaires locaux au
PATHpour que Python trouve les paquets. - Nous configurons le reste (ENV, CMD, etc.).
L'avantage majeur est que l'image finale ne contient aucun outil de compilation ni cache généré par pip. Elle ne contient que le strict nécessaire, ce qui réduit sa taille.
- Construisons cette nouvelle image :
docker build -t multi-stage-flask-app .
- Comparons maintenant la taille de nos deux images :
docker images | grep flask-app
multi-stage-flask-app latest 7bdd1be2d1fb 10 seconds ago 129MB
advanced-flask-app latest c59d6fa303cc 10 minutes ago 136MB
Vous constaterez que multi-stage-flask-app est plus légère que advanced-flask-app.
- Lançons un conteneur avec cette image optimisée :
docker run -d -p 5001:5000 --name multi-stage-container multi-stage-flask-app
Notez que nous utilisons le port 5001 pour éviter tout conflit avec le conteneur précédent.
- Testez l'application :
curl http://localhost:5001
Le message "Hello from production environment!" doit toujours s'afficher.
- Pour mieux comprendre les différences de structure, utilisez la commande
docker history:
docker history advanced-flask-app
docker history multi-stage-flask-app
En comparant les sorties, vous remarquerez que le build multi-étapes comporte moins de couches et que certaines sont beaucoup plus petites. C'est une technique indispensable pour les langages compilés ou les applications ayant des processus de construction complexes.
Utilisation du Fichier .dockerignore
Lors de la construction d'une image, Docker envoie tous les fichiers du répertoire courant au démon Docker (le "build context"). Si vous avez des fichiers volumineux inutiles, cela ralentit le processus. Le fichier .dockerignore permet d'exclure certains fichiers ou répertoires.
Créons-en un pour voir son fonctionnement :
- Dans l'IDE Web, créez un nouveau fichier dans le dossier
advanced-dockerfilenommé.dockerignore. - Ajoutez le contenu suivant :
**/.git
**/.gitignore
**/__pycache__
**/*.pyc
**/*.pyo
**/*.pyd
**/.Python
**/env
**/venv
**/ENV
**/env.bak
**/venv.bak
Signification des motifs :
**/.git: Ignore le répertoire .git et tout son contenu, peu importe où il se trouve.**/.gitignore: Ignore les fichiers .gitignore.**/__pycache__: Ignore les caches Python.**/*.pyc,**/*.pyo,**/*.pyd: Ignore les fichiers Python compilés.**/venv,**/env: Ignore les environnements virtuels locaux.
Le préfixe ** signifie "dans n'importe quel sous-répertoire".
- Pour tester l'effet, créons des fichiers factices que nous souhaitons ignorer :
mkdir venv
touch venv/ignore_me.txt
touch .gitignore
Ces fichiers sont courants dans les projets Python mais n'ont pas leur place dans une image Docker de production.
- Reconstruisez l'image :
docker build -t ignored-flask-app .
- Pour vérifier que ces fichiers n'ont pas été inclus, examinez l'historique :
docker history ignored-flask-app
Vous ne devriez voir aucune étape copiant le répertoire venv ou le fichier .gitignore. Le fichier .dockerignore est essentiel pour garder vos images propres et vos builds rapides.
Instructions Dockerfile Avancées
Dans cette dernière étape, nous allons explorer des instructions qui renforcent la sécurité, la maintenabilité et la robustesse de vos images.
- Dans l'IDE Web, ouvrez à nouveau le
Dockerfile. - Remplacez le contenu par celui-ci :
## Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
## Final stage
FROM python:3.9-slim
## Create a non-root user
RUN useradd -m appuser
## Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
## Dynamically determine Python version and site-packages path
RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && \
SITE_PACKAGES_PATH="/home/appuser/.local/lib/python${PYTHON_VERSION}/site-packages" && \
mkdir -p "${SITE_PACKAGES_PATH}" && \
chown -R appuser:appuser /home/appuser/.local
## Copy site-packages and binaries using the variable
COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}"
COPY --from=builder /root/.local/bin /home/appuser/.local/bin
COPY app.py .
ENV PATH=/home/appuser/.local/bin:$PATH
ENV ENVIRONMENT=production
## Set the user to run the application
USER appuser
## Use ENTRYPOINT with CMD
ENTRYPOINT ["python"]
CMD ["app.py"]
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:5000/ || exit 1
ARG BUILD_VERSION
LABEL maintainer="Your Name <your.email@example.com>"
LABEL version="${BUILD_VERSION:-1.0}"
LABEL description="Flask app demo with advanced Dockerfile techniques"
Décortiquons les nouveaux concepts :
RUN useradd -m appuser: Crée un utilisateur non-root. C'est une règle de sécurité majeure : si votre application est compromise, l'attaquant n'aura pas les privilèges root sur le système.RUN apt-get update && ... && rm -rf /var/lib/apt/lists/*: Installecurlpour le test de santé (healthcheck) et nettoie immédiatement les fichiers temporaires pour ne pas alourdir l'image.USER appuser: Indique à Docker d'exécuter les instructions suivantes (et le processus final) avec cet utilisateur restreint.ENTRYPOINT ["python"]avecCMD ["app.py"]:ENTRYPOINTdéfinit l'exécutable principal, tandis queCMDfournit les arguments par défaut. Cela permet à l'utilisateur de passer facilement d'autres arguments au démarrage s'il le souhaite.HEALTHCHECK: Configure un test périodique pour vérifier que l'application répond toujours. Si la commandecurléchoue, Docker marquera le conteneur comme "unhealthy".ARG BUILD_VERSION: Définit une variable disponible uniquement pendant la construction.LABEL version="${BUILD_VERSION:-1.0}": Utilise l'argument de build pour étiqueter l'image. Si aucune valeur n'est fournie, elle prend "1.0" par défaut.
- Construisons cette image en passant une version spécifique :
docker build -t advanced-flask-app-v2 --build-arg BUILD_VERSION=2.0 .
- Vérifiez la création de l'image :
docker images | grep advanced-flask-app-v2
- Lancez le conteneur :
docker run -d -p 5002:5000 --name advanced-container-v2 advanced-flask-app-v2
- Vérifiez son état :
docker ps | grep advanced-container-v2
Si le conteneur ne semble pas actif, utilisez docker ps -a pour voir s'il s'est arrêté prématurément, puis consultez les logs avec docker logs advanced-container-v2.
- Après quelques secondes (le temps que le premier test de santé s'exécute), vérifiez le statut de santé :
docker inspect --format='{{.State.Health.Status}}' advanced-container-v2
Vous devriez voir "healthy".
- Vérifiez que le label de version a bien été appliqué :
docker inspect -f '{{.Config.Labels.version}}' advanced-container-v2
La sortie devrait être "2.0".
- Enfin, testez l'accès final :
curl http://localhost:5002
Ces techniques avancées transforment un simple conteneur en une image prête pour la production, sécurisée et facile à surveiller.
Résumé
Dans cet atelier, nous avons exploré des techniques avancées de Dockerfile pour créer des images plus performantes, sécurisées et maintenables. Nous avons appris à :
- Analyser l'impact des instructions sur les couches de l'image pour optimiser le cache et le stockage.
- Utiliser les constructions multi-étapes (multi-stage builds) pour séparer l'environnement de compilation de l'environnement d'exécution, réduisant ainsi drastiquement la taille des images.
- Configurer un fichier
.dockerignorepour exclure les fichiers inutiles du contexte de construction. - Implémenter des instructions avancées comme
USERpour la sécurité,ENTRYPOINTpour la flexibilité,HEALTHCHECKpour la surveillance etARGpour la personnalisation lors du build.
Ces compétences vous permettent désormais de produire des conteneurs de qualité professionnelle, adaptés aux exigences des environnements de production modernes.



