Ecrire un Dockerfile
Mise à jour :
Un Dockerfile est l’élément fondamental pour automatiser la création d’images Docker. Bien conçu, il permet de garantir des déploiements rapides, fiables et sécurisés. Dans ce guide, nous explorerons les meilleures pratiques pour rédiger un Dockerfile efficace, afin que vos applications puissent être déployées de manière optimale.
Concepts Fondamentaux
Avant de plonger dans l’écriture d’un Dockerfile, il est indispensable de bien comprendre les concepts fondamentaux liés aux images de conteneurs. Une bonne maîtrise de ces notions vous permettra d’écrire des Dockerfiles plus efficaces et optimisés.
Si vous n’êtes pas encore familier avec les concepts de base, je vous invite à consulter le guide précédent Concepts Fondamentaux des Images de Conteneurs. Ce guide couvre en détail des éléments essentiels tels que :
- Qu’est-ce qu’une image de conteneur ? Une vue d’ensemble sur la structure et le rôle d’une image de conteneur dans la conteneurisation.
- Structure des images de conteneurs : Comprendre les couches qui composent une image et comment elles interagissent.
- Choix de l’image de base : Pourquoi le choix d’une image de base est important et comment faire un choix éclairé.
- Instructions fondamentales dans un Dockerfile : Un aperçu des commandes de
base telles que
FROM
,RUN
,COPY
,CMD
et comment elles fonctionnent ensemble. - Optimisation de la taille des images : Les meilleures pratiques pour garder vos images légères et performantes.
- Construction et distribution des images : Les étapes finales pour construire une image localement et la partager via un registre de conteneurs.
En vous familiarisant avec ces concepts, vous serez mieux préparé à comprendre et à appliquer les bonnes pratiques lors de l’écriture de votre Dockerfile. Le guide précédent est un excellent point de départ pour consolider ces bases avant de continuer avec les aspects plus pratiques et techniques abordés dans ce guide.
La syntaxe d’un Dockerfile
Un Dockerfile est un fichier texte qui décrit, étape par étape, comment construire une image de conteneur. Chaque ligne du Dockerfile correspond à une instruction qui précise une action à exécuter pour configurer l’image. Ces instructions suivent une syntaxe simple : Instruction suivie d’un ou plusieurs arguments, qui définissent les détails de l’action.
Par exemple, l’instruction FROM
indique l’image de base sur laquelle sera
construite l’image finale, tandis que COPY
permet de copier des fichiers
depuis votre machine locale vers l’image. L’ordre dans lequel ces instructions
sont placées est important, car il détermine la séquence des étapes de
construction et la structure finale de l’image.
Un Dockerfile typique peut contenir des instructions comme RUN
pour
exécuter des commandes dans le conteneur, ENV
pour définir des variables
d’environnement, ou encore CMD
pour spécifier la commande à exécuter lorsque
le conteneur démarre. Ces instructions sont essentielles pour personnaliser et
optimiser l’image de conteneur en fonction des besoins spécifiques de votre
application.
Les instructions Dockerfile
L’instruction FROM
Pour qu’un fichier Dockerfile
soit valide, il doit commencer avec
l’instruction FROM
qui définit l’image de base. Cette image de base peut être
n’importe quelle image valide.
ou
ou
L’argument facultatif --platform
permet de spécifier la plate-forme de
l’image dans le cas où FROM
fait référence à une image multiplateforme. Par
exemple, linux/amd64
. Par défaut, la plateforme utilisée est celle où est
effectuée la construction de l’image.
Les valeurs tag
ou digest
sont facultatives. Si vous omettez l’un ou
l’autre, le générateur suppose que vous voulez récupérer l’image possédant le
tag latest
.
L’instruction FROM
peut apparaître plusieurs fois dans un Dockerfile
. Nous
verrons plus tard ce principe qui permet d’optimiser la taille des images
produites avec ce qu’on appelle le multi-stage.
L’instruction LABEL
L’instruction LABEL
permet d’ajouter des informations, des métadonnées, à
l’image sous forme de clés / valeurs.
Les étiquettes incluses dans les images de base sont transmises aux images produites. Si une étiquette existe, la valeur appliquée sera la plus récente.
L’instruction ARG
L’instruction ARG
est la seule instruction qui peut précéder l’instruction
FROM
. Elle permet de définir des variables qui peuvent être transmises au
moment de la construction de l’image.
Il est possible de définir des valeurs par défaut avec l’opérateur =
suivi de
cette valeur.
Pour utiliser la variable, on utilise la même syntaxe qu’on utilise dans les
scripts shell
:
Attention à la portée des variables ARG
. En effet, une variable entre en
vigueur à partir de la ligne sur laquelle elle est définie !
Les instructions FROM
peuvent utiliser des variables déclarées avant elle.
L’instruction ENV
Contrairement à ARG
, l’instruction ENV
permet de définir des variables
d’environnement qui seront utilisés dans les commandes exécutées avec
l’instruction RUN
que nous verrons juste après. Ces variables sont
persistantes et seront disponibles au moment de l’exécution de l’image de
conteneur.
Cela peut être très utile configurer des services faisant appel à des variables
d’environnement. Par exemple pour une image proposant un service de Base De
Données Postgres la variable d’environnement PGDATA
qui indique où se trouvent
les données des bases.
Elles peuvent utiliser des variables définies avec ARG
.
Les variables d’environnement définies avec ENV
seront disponibles au moment
de l’exécution de l’image du conteneur. Une étape hérite de toutes les variables
d’environnement définies dans l’étape parent. Donc attention cela peut provoquer
des comportements inattendus.
Si une variable d’environnement n’est nécessaire que lors de la construction, et non dans l’image finale, envisagez plutôt de définir une valeur pour une seule commande :
ou en utilisant l’instruction ARG
vu précédemment
L’instruction RUN
L’instruction RUN
permet de lancer des commandes aux moments de la
construction de l’image.
Par exemple pour installer des packages depuis une image de base Alpine :
Pour optimiser la taille des images, car chaque instruction RUN ajoute une
couche à l’image résultante, il est conseillé d’enchainer les commandes en les
séparant avec la séquence &&
.
Lorsque beaucoup de commande s’enchainent, on peut utiliser un antislash \
pour poursuivre les commandes de l’instruction RUN sur la ligne suivante.
L’instruction COPY
L’instruction COPY
permet d’intégrer des fichiers et ou des dossiers pour les
ajouter au système de fichiers de l’image dans le répertoire <dest>
.
La seconde forme est à utiliser si vous avez des espaces dans les noms de fichiers. Comme vous pouvez le remarquer, on peut indiquer plusieurs sources dans une seule instruction.
L’instruction ADD
L’instruction ADD
fonctionne comme l’instruction COPY
sauf qu’il permet
d’intégrer des URL de fichiers distants et/ou des archives qui seront
décompressés à la volée et les ajoute au système de fichiers de l’image dans le
répertoire <dest>
.
L’instruction USER
L’instruction USER
définit l’utilisateur ou l’UID et éventuellement le groupe
d’utilisateurs ou le GID à utiliser pour le reste de l’étape en cours.
L’utilisateur spécifié est utilisé pour les instructions RUN
et, au moment de
l’exécution de l’image de conteneur.
Un exemple permettant de spécifier l’UID et le GID via des ARG’s :
L’instruction WORKDIR
L’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éé.
L’instruction VOLUME
L’instruction VOLUME
crée un point de montage avec le nom spécifié et le
marque comme contenant des
volumes montés en externe à
partir d’un hôte natif ou d’autres conteneurs.
Les volumes sont utiles pour stocker des données qui doivent persister même si le conteneur est arrêté ou supprimé.
L’instruction EXPOSE
L’instruction EXPOSE
informe le moteur de conteneur que le conteneur écoute
sur les ports réseau spécifiés au moment de l’exécution. Vous pouvez spécifier
le protocole TCP ou UDP, TCP étant la valeur par défaut.
Les instructions CMD et ENTRYPOINT
Ces deux instructions permettent de définir les commandes qui seront exécutées au moment du lancement de l’image de conteneur.
La principale différence entre CMD
et ENTRYPOINT
est que les commandes
fournies par CMD peuvent être remplacés, alors que celles fournies par
ENTRYPOINT ne le peuvent pas à moins de les forcer avec le bon argument dans la
CLI de votre moteur de conteneur.
Exemple :
Construire une première image
Dans ce chapitre, nous allons voir comment construire une image Docker basique pour une application Python. Ce guide est parfait pour ceux qui débutent avec Docker et souhaitent comprendre les concepts fondamentaux en construisant une image simple et fonctionnelle. Nous allons utiliser un Dockerfile minimaliste pour créer une image capable de lancer une application Python.
Choix de l’Image de Base
Le point de départ de tout Dockerfile est l’image de base. Pour une application Python, nous allons utiliser une image officielle de Python disponible sur Docker Hub.
Ici, nous utilisons python:3.11-slim
, une image allégée de Python basée
sur la version 3.11. L’utilisation de l’option “slim” permet de réduire la
taille de l’image, ce qui est idéal pour des déploiements rapides et efficaces.
Configuration du Répertoire de Travail
Ensuite, nous allons définir un répertoire de travail dans lequel toutes les commandes suivantes s’exécuteront. Cela simplifie la gestion des fichiers dans l’image.
Cette instruction WORKDIR /app
crée un répertoire /app
et positionne le
conteneur à cet emplacement. Toutes les opérations suivantes, comme la copie de
fichiers ou l’installation de dépendances, se feront dans ce répertoire.
Copie du Code Source
Nous devons maintenant copier notre code source et notre fichier de dépendances
dans l’image. Supposons que votre application Python a un fichier
requirements.txt
qui liste les dépendances nécessaires.
COPY requirements.txt .
: Copie le fichierrequirements.txt
depuis votre machine locale vers le répertoire de travail du conteneur.COPY . .
: Copie l’intégralité du code source de votre projet dans le répertoire de travail du conteneur.
Installation des Dépendances
Une fois le code source copié, nous devons installer les dépendances nécessaires
à l’exécution de l’application. Cela se fait en utilisant pip
, le gestionnaire
de paquets Python.
L’instruction RUN pip install --no-cache-dir -r requirements.txt
installe
toutes les dépendances listées dans requirements.txt
. L’option
--no-cache-dir
évite de stocker en cache les paquets téléchargés, ce qui
permet de garder l’image plus légère.
Définition du Point d’Entrée
Enfin, nous devons définir la commande qui sera exécutée lorsque le conteneur
démarrera. Supposons que notre application Python démarre avec un fichier appelé
app.py
.
L’instruction CMD ["python", "app.py"]
spécifie que, par défaut, le
conteneur exécutera python app.py
lorsque vous le lancerez. Cette commande
lancera votre application Python.
Bonnes Pratiques pour Écrire un Dockerfile de Qualité
Pour écrire des Dockerfiles performants, maintenables et sécurisés, il est essentiel de suivre certaines bonnes pratiques. Ces pratiques vous aideront à optimiser vos images, réduire leur taille et minimiser les erreurs. En complément de ces bonnes pratiques, l’utilisation d’outils de contrôle vous permettra de perfectionner votre maîtrise de la création d’images Docker. Voici quelques conseils et outils incontournables pour progresser.
Écrire des Dockerfiles Simples et Lisibles
La simplicité est clé pour écrire des Dockerfiles maintenables. Utilisez des instructions claires et évitez les combinaisons complexes de commandes sur une seule ligne. Chaque commande doit être explicite et facile à comprendre pour quiconque lit le Dockerfile.
Exemple :
Cette commande est claire, installe les paquets nécessaires et nettoie ensuite pour réduire la taille de l’image.
Minimiser la Taille de l’Image
Utiliser des images de base légères, comme alpine
et combiner les commandes
pour réduire le nombre de couches sont des stratégies essentielles pour
minimiser la taille de l’image. Moins l’image est volumineuse, plus elle est
rapide à déployer et à transférer.
Exemple :
En utilisant alpine
, une version allégée de Linux, vous réduisez
considérablement la taille de l’image finale.
Utiliser .dockerignore
Comme pour .gitignore
, un fichier .dockerignore
permet d’exclure les
fichiers inutiles lors de la construction de l’image, ce qui contribue à réduire
sa taille et à éviter l’inclusion de données sensibles.
Exemple :
Ces fichiers ne seront pas inclus dans l’image Docker, ce qui la rendra plus légère et plus sécurisée.
Séparer les Phases de Construction et d’Exécution
Utiliser une approche multi-étape pour séparer la phase de construction (qui inclut les outils et dépendances de développement) de la phase d’exécution permet de réduire la taille de l’image finale tout en améliorant la sécurité.
Exemple :
Dans cet exemple, la compilation est effectuée dans une première étape, puis le binaire compilé est copié dans une image alpine légère pour l’exécution.
Gérer les Variables d’Environnement avec Précaution
Définissez les variables d’environnement (ENV
) et les arguments de
construction (ARG
) pour gérer les configurations, mais évitez d’y inclure des
informations sensibles comme des clés API ou des mots de passe.
Exemple :
Cette configuration permet de gérer facilement les environnements de développement et de productixon.
Utiliser des Outils de Contrôle
Pour garantir la qualité et l’efficacité de vos Dockerfiles, il est important d’utiliser des outils de contrôle qui vous aideront à respecter les bonnes pratiques et à optimiser vos images.
Hadolint
Hadolint est un outil de linting qui analyse vos Dockerfiles et vous donne des recommandations pour améliorer leur qualité. Il vérifie si vous suivez les bonnes pratiques et vous alerte sur les erreurs courantes.
Vous pouvez découvrir comment utiliser Hadolint dans ce billet.
Dive
dive ↗ est un outil d’analyse qui vous permet de plonger dans les couches de votre image Docker pour comprendre comment elle est construite. Il vous aide à identifier les couches inutiles et à optimiser la taille de votre image.
Pour en savoir plus sur l’utilisation de dive, consultez la section dédiée à l’optimisation de la taille des images de conteneurs.
Conteneur-Structure-Test
Conteneur-Structure-Test est un outil qui permet de valider la structure de vos images Docker. Il peut vérifier l’exécution des commandes, les métadonnées, et le contenu du système de fichiers pour s’assurer que votre image est conforme à vos attentes.
Toutes les informations sur conteneur-structure-test
sont disponibles dans ce
billet.
Conclusion
En suivant ces bonnes pratiques et en utilisant les outils appropriés, vous êtes désormais prêt à créer des Dockerfiles de haute qualité. Ces outils et techniques vous aideront à progresser et à garantir que vos images Docker restent performantes, sécurisées et faciles à gérer.
Pour aller plus loin, vous pouvez explorer comment optimiser la taille des images ou découvrir les différents moteurs de conteneurs.