Un Dockerfile est simplement un fichier texte qui enchaîne des instructions destinées à installer tout ce dont notre application a besoin pour fonctionner.
La liste d’instructions disponibles est assez courte, voire une dizaine d’instructions, mais c’est suffisant pour construire une image complète.
On peut (je dirais, “On doit”) y ajouter des commentaires pour documenter ce que l’on fait afin aussi de facilité.
Jusqu’ici nous avons utilisé des images toutes prêtes téléchargées sur Docker Hub
Une des fonctionnalités principales de Docker est de pouvoir facilement construire des images à partir d’un simple fichier texte : le Dockerfile.
Une image Docker ressemble un peu à un template de VM, car on peut penser à un Linux figé dans un état.
On construit les images à partir d’un fichier Dockerfile en décrivant procéduralement (étape par étape) la construction.
Les images sont créées en empilant de nouvelles couches sur une image existante grâce à un système de fichiers qui fait du union mount.
Chaque nouveau build génère une nouvelle image dans le répertoire des images (/var/lib/docker/images) (attention ça peut vite prendre énormément de place)
Privilégiez les images slim ou alpine pour réduire la taille finale. (Contiens une version très légère de Linux)
FROM python:3.11-slim
COPY . /app
RUN pip install -r /app/requirements.txt
Voici ce que fait Docker :
Chaque couche est immutabilité et cacheable. Si on relance le build sans changer les fichiers copiés, Docker réutilisera les couches précédentes.
L’ordre des instructions dans le Dockerfile impacte directement le cache. Placez les instructions qui changent rarement (installation de dépendances système) en haut, et celles qui changent souvent (copie du code source) en bas. Ainsi, le cache est réutilisé au maximum.
docker build [-t tag] [-f dockerfile] <build_context>
généralement pour construire une image on se place directement dans le dossier avec le Dockerfile et les éléments de contexte nécessaire (programme, config, etc), le contexte est donc le caractère ., il est obligatoire de préciser un contexte.
exemple : docker build -t mon-debian .
Voici un exemple complet de l’installation d’un projet Python
# Image de base : on utilise une distribution Alpine légère
FROM alpine:3.5
# Définition du répertoire de travail pour centraliser l'application
# Répertoire dans lequel toutes les commandes suivantes s'exécuteront
WORKDIR /usr/src/app
# Installation de Python 2 et de pip, puis mise à jour de pip (apk fait la même chose que apt en plus léger ... apk est le gestionnaire de paquets d’Alpine Linux )
# (Regrouper les installations permet de limiter le nombre de couches de l'image)
RUN apk add --update py2-pip && \
pip install --upgrade pip
# Installation des dépendances Python
# On copie d'abord uniquement le fichier requirements.txt pour mettre en cache
# C'est le fichier des dépendances Python
COPY requirements.txt ./
# Installation des dépendances Python sans cache pour réduire la taille de l'image
RUN pip install --no-cache-dir -r requirements.txt
# Copie des fichiers de l'application
# Ce sont les sources et les templates nécessaires à l'application python qui a été développé
COPY app.py ./
COPY templates/index.html ./templates/
# Indication du port sur lequel l'application écoute à l'intérieur du conteneur
EXPOSE 5000
# Commande de démarrage pour lancer l'application Python
CMD ["python", "app.py"]
FROMFROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
RUNRUN apk update
RUN apk add nginx
ADD ou COPYCOPY sont plus complètent, et ADD permettent d’extraire automatiquement des archives locales (.tar).COPY [--chown=<user>:<group>] [--chmod=<perms>] <src>... <dest>
#ou
COPY [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
ADD [--chown=<user>:<group>] [--chmod=<perms>] [--checksum=<checksum>] <src>... <dest>
# ou
ADD [--chown=<user>:<group>] [--chmod=<perms>] ["<src>",... "<dest>"]
CMDDockerfile : elle permet de préciser la commande par défaut lancé à la création d’une instance du conteneur avec docker run. on l’utilise avec une liste de paramètresL’instruction CMD a trois formes :
CMD ["executable","param1","param2"] (exec form, forme à préférer)CMD ["param1","param2"] (combinée à une instruction ENTRYPOINT)CMD command param1 param2 (shell form)CMD ["echo 'Conteneur démarré'"]
ENTRYPOINTCMD ["executable","param1","param2"]
ENTRYPOINT ["executable", "param1", "param2"]
CMD et ENTRYPOINTRUN qui exécute une commande Dockerfile uniquement pendant la construction de l’image.CMD et ENTRYPOINT c’est que cmd peut avoir des paramètres dynamiques et entrepoint doit avoir des paramètres statiquesExemple de combinaison de CMD et ENTRYPOINT
FROM alpine:3.20
ENTRYPOINT ["echo"]
CMD ["Conteneur démarré"]
docker build -t demo .
docker run demo # résultat : Bonjour Docker!
docker run demo "Yo patate !" # résultat : Yo patate !
ARGARG est la seule instruction qui peut précéder l’instruction FROM.ARG <name>[=<default value>]
#exemple
ARG version=1.15.3-alpine@sha256:829a63ad2b1389e393e5decf5df25860347d09643c335d1dc3d91d25326d3067
RUN echo ${version}
ENVENV <key>=<value> ...
#exemple
ENV PGDATA=/data
On peut utiliser des variables d’environnement dans les Dockerfiles. La syntaxe est ${...}.
Exemple :
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO} # WORKDIR /bar
ADD . $FOO # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux
Se référer au mode d’emploi pour la logique plus précise de fonctionnement des variables.
USERUSER <user>[:<group>]
#ou
USER UID[:GID]
# Utilise une image Alpine précise via son digest pour garantir une version immuable
FROM alpine@sha256:d7342993700f8cd7aba8496c2d0e57be0666e80b4c441925fc6f9361fa81d10e
# Définit l’UID du futur utilisateur (par défaut 1000)
ARG USER_UID=1000
# Définit le GID, identique à l’UID sauf si modifié
ARG USER_GID=${USER_UID}
# Crée un groupe et un utilisateur, installe sudo, et configure l’accès root sans mot de passe
RUN groupadd --gid $USER_GID $USERNAME \ # Crée un groupe avec le GID fourni
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ # Crée l’utilisateur avec home directory
&& apk update \ # Met à jour l’index des paquets (APT — attention, Alpine n’utilise pas APT)
&& apk install sudo \ # Installe sudo
&& echo "$USERNAME ALL=(root) NOPASSWD:ALL" > /etc/sudoers.d/$USERNAME \ # Autorise sudo sans mot de passe
&& chmod 0440 /etc/sudoers.d/$USERNAME # Sécurise le fichier sudoers
# Définit l’utilisateur par défaut pour les prochaines instructions
USER $USERNAME
WORKDIRL’instruction WORKDIR définit le répertoire de travail pour toutes les instructions qui la suivent dans le Dockerfile. Si le répertoire n’existe pas, il sera créé.
WORKDIR /path/to/workdir
HEALTHCHECKHEALTHCHECK permet de vérifier si l’app contenue dans un conteneur est en bonne santé.
HEALTHCHECK CMD curl --fail http://localhost:5000/health
VOLUMEEXPOSEEXPOSE <port> [<port>/<protocol>...]
BUILD - Lancer la constructiondocker build [-t <tag:version>] [-f <chemin_du_dockerfile>] <contexte_de_construction>
Lors de la construction, Docker télécharge l’image de base. On constate plusieurs téléchargements en parallèle.
Il lance ensuite la séquence des instructions du Dockerfile.
Observez l’historique de construction de l’image avec docker image history <image>
Il lance ensuite la série d’instructions du Dockerfile et indique un hash pour chaque étape.
Les images Docker ont souvent une taille de plusieurs centaines de mégaoctets voire parfois gigaoctets. docker image ls permet de voir la taille des images.
Or, on construit souvent plusieurs dizaines de versions d’une application par jour (souvent automatiquement sur les serveurs d’intégration continue).
Le principe de Docker est justement d’avoir des images légères, car on va créer beaucoup de conteneurs (un par instance d’application/service).
De plus on télécharge souvent les images depuis un registry, ce qui consomme de la bande passante.
La principale bonne pratique dans la construction d’images est de limiter leur taille au maximum.
Choisir une image Linux de base minimale:
ubuntu complète pèse déjà presque une soixantaine de mégaoctets.busybox) est difficile à déboguer et peu bloquer pour certaines tâches à cause de binaires ou de bibliothèques logicielles qui manquent (compilation par exemple).alpine qui est un bon compromis (6 mégaoctets seulement et un gestionnaire de paquets apk).python3 est fourni en version python:alpine (99 Mo), python:3-slim (179 Mo) et python:latest (918 Mo).Il n’est pas nécessaire de partir d’une image Linux vierge pour construire un conteneur.
On peut utiliser la directive FROM avec n’importe quelle image.
De nombreuses applications peuvent être configurées en étendant une image officielle
Exemple : une image Wordpress déjà adaptée à des besoins spécifiques.
L’intérêt ensuite est que l’image est disponible préconfigurée pour construire ou mettre à jour une infrastructure, ou lancer plusieurs instances (plusieurs conteneurs) à partir de cette image.
C’est grâce à cette fonctionnalité que Docker peut être considéré comme un outil d'infrastructure as code.
On peut également prendre une sorte de snapshot du conteneur (de son système de fichiers, pas des processus en train de tourner) sous forme d’image avec docker commit <container> <image> et docker push.
###############################################
# 1. Image de base
###############################################
# Alpine : image légère, rapide, idéale pour comprendre Docker
FROM alpine:3.20
###############################################
# 2. Variables de construction (build arguments)
###############################################
# UID du futur utilisateur non-root.
# 1000 correspond généralement au premier utilisateur “humain” sur Linux.
# Cela évite que le conteneur crée des fichiers appartenant à root sur l’hôte.
# Bonne pratique : exécuter les applications Docker avec un utilisateur non-root.
ARG USER_UID=1000
# GID du groupe principal de l’utilisateur.
# UID et GID sont deux identités distinctes dans Linux (utilisateur vs groupe).
# On les sépare pour garder un Dockerfile flexible et compatible avec différents systèmes.
# Même si la valeur est identique, cela évite des problèmes de permissions avec des volumes.
ARG USER_GID=${USER_UID}
# Nom de l’utilisateur
ARG USERNAME=appuser
###############################################
# 3. Installation des dépendances
###############################################
# Mise à jour des dépôts + installation de bash et curl
RUN apk update \
&& apk add --no-cache bash curl
###############################################
# 4. Création d’un utilisateur non-root
###############################################
# Création du groupe et de l’utilisateur avec UID/GID définis plus haut
RUN addgroup -g $USER_GID $USERNAME \
&& adduser -D -u $USER_UID -G $USERNAME $USERNAME
###############################################
# 5. Copie des fichiers dans l’image
###############################################
# On copie un script de démarrage dans l’image
COPY ./start.sh /usr/local/bin/start.sh
###############################################
# 6. Permissions
###############################################
# On rend le script exécutable
RUN chmod +x /usr/local/bin/start.sh
###############################################
# 7. Définition de l’utilisateur par défaut
###############################################
# Toutes les commandes suivantes seront exécutées par l’utilisateur non-root
USER $USERNAME
###############################################
# 8. Définition du répertoire de travail
###############################################
# Répertoire de travail par défaut
WORKDIR /home/$USERNAME
###############################################
# 9. Commande de démarrage du conteneur
###############################################
# Le conteneur exécutera ce script au démarrage
CMD ["/usr/local/bin/start.sh"]