7. Créer une image Docker avec le Dockerfile - Docker
Comme Docker est un outil qui permet de "packager" un conteneur avec toutes ses dépendances et y inclure ce que l'on souhaite dans une image, nous allons voir de quoi est composée celle-ci pour y inclure dans notre exemple, des modules Python supplémentaires.
Les Layers
Pour construire une image Docker, il faut fournir des instructions dans un fichier nommé Dockerfile
.
Chaque instruction ajoute une couche (ou layer) à l'image Docker.
Dans ce schéma, nous pouvons voir plusieurs lignes qui correspondent à chaque layer de l'image Docker. Chaque layer occupe un certain espace disque, et lui est attribué un hash.
Chacune des instructions ( FROM
, RUN
, COPY
CMD
etc...) correspond à une étape dans la construction de l'image.
Ces étapes correspondent par exemple à la copie d'un fichier, l'exécution d'une commande etc...
Le Dockerfile
Toujours dans notre exemple avec l'image Docker Python, j'ai un nouveau besoin, celui d'avoir le module Python requests
de manière permanente dans mon image.
C'est-à-dire que lorsque je lancerai mon conteneur Python, si je le supprime et que je le recrée, même dans un nouvel environnement, le module Python requests
devra être contenu dedans.
Plutôt que de lancer un conteneur Python, et d'exécuter la commande pip install requests
à chaque lancement du conteneur, nous allons construire notre propre image Docker à partir de l'image Python.
Notre script main.py
correspond à ceci :
import requests
import json
url = "https://jsonplaceholder.typicode.com/posts"
data = {
"title": "foo",
"body": "bar",
"userId": 1
}
headers = {
"Content-type": "application/json; charset=UTF-8"
}
response = requests.post(url, data=json.dumps(data), headers=headers)
print(response.json())
Pour la construction de notre image, nous allons créer un fichier nommé Dockerfile
et ajouter cette première ligne :
FROM python
L'instruction FROM
est la première ligne qui doit figurer obligatoirement dans votre Dockerfile
. Elle indique à partir de quelle autre image Docker nous allons nous baser.
Cela va donc effectuer un docker pull
de l'image mentionnée. Je n'ai pas précisé de tag
, ce sera donc l'image python:latest
qui sera utilisée.
Installons maintenant le module Python requests
en ajoutant l'instruction RUN
dans notre Dockerfile
:
RUN pip install requests
L'instruction RUN
exécute des commandes nécessaires à la construction de l'image. C'est comme si vous étiez connecté au conteneur avec un Shell et que vous exécutiez des commandes.
Notre image Python contient maintenant le module requests
. Il est possible de passer à la suite, mais avant cela, regardons ensemble quelques instructions essentielles à la construction d'une image Docker.
Créons un utilisateur pour notre image car par défaut, c'est l'utilisateur root
qui est utilisé. Ajoutons une deuxième instruction RUN
avec la commande useradd
comme ceci :
RUN useradd -d /home/python-user -m -s /bin/bash python-user
Définissons le comme utilisateur par défaut afin qu'il exécute nos instructions. Ajoutons l'instruction USER
dans notre fichier Dockerfile
comme ceci :
USER python-user
Précisons le répertoire courant par défaut dans lequel notre image se trouvera. Ajoutons l'instruction WORKDIR
dans notre Dockerfile
:
WORKDIR /python-user
Copions notre script main.py
pour qu'il soit présent dans notre conteneur. C'est l'instruction COPY
que nous allons ajouter à notre Dockerfile
en précisant le répertoire cible :
COPY main.py /python-user
Maintenant que le script Python est contenu dans notre image, ajoutons l'instruction CMD
pour spécifier la commande par défaut qui sera exécutée dans notre image. Dans notre cas, cette instruction servira plutôt d'argument à notre commande par défaut.
CMD ["main.py"]
Nous allons coupler cela avec l'instruction ENTRYPOINT
Cela correspond à la commande qui sera exécutée constamment au démarrage du conteneur.
L'ENTRYPOINT
par défaut de l'image Python est la commande python3
, et comme nous avons spécifié la commande a exécuter, ce sera notre script main.py
qui sera exécuté.
ENTRYPOINT ["python3"]
Le Build
Nous avons maintenant un Dockerfile
qui correspond à ceci :
FROM python
RUN pip install requests
RUN useradd -d /home/python-user -m -s /bin/bash python-user
USER python-user
WORKDIR /home/python-user
COPY main.py /home/python-user
CMD ["main.py"]
ENTRYPOINT ["python3"]
Maintenant que nous avons défini toutes les instructions nécessaires à la construction de notre image, place au build.
Nous allons utiliser la commande docker build
afin de construire une image Docker à partir d'un Dockerfile
. Pour ce faire, plaçons nous dans le répertoire contenant notre Dockerfile
, et lançons la commande suivante :
docker build -t python-requests:1.0.0 .
-t
: Spécifie l'utilisation d'un tag Docker.python-requests
: Nom de notre image1.0.0
: Tag de l'image. Nous utilisons ici le Semantic Versioning (SemVer)..
: Correspond au contexte, à l'emplacement de notreDockerfile
.
A l'exécution de cette commande, vous verrez chacun des layers se construire pour obtenir l'image finale.
Lorsque le build est terminé, vous pouvez lancer la commande suivante afin d'observer les images Docker disponibles en local.
docker images
Vous verrez alors l'image que vous venez de construire précédemment. Vous pouvez donc maintenant lancer un conteneur avec la commande docker run
à partir de cette image.
docker commit
. Cela consiste à lancer un conteneur, effectuer les actions que l'on souhaite dedans et créer une nouvelle image à partir de l'état de ce conteneur.Je n'ai pas abordé en détail cette méthode car elle n'est pas forcément conseillée. En effet, il est préférable d'utiliser des
Dockerfile
.Nous avons vu les instructions les plus utilisées mais il en existe bien d'autres. Je vous laisserai vous documenter sur celles-ci lorsque vous en aurez besoin.
Dans ce cours, vous avez pu construire votre propre image Docker ! Sauf que je ne vous ai pas montré les bonnes pratiques à utiliser pour construire une image Docker.
Oui car il est possible d'optimiser une image Docker de bien des manières :
- Optimiser les layers avec des opérateurs Bash comme
&&
. - Optimiser la taille d'une image Docker avec des arguments sur l'outil d'installation (
apt install --no-cache
par exemple). - Effectuer du build multi-stage (niveau avancé).
- Utiliser des variables d'environnement et des arguments de build
Et bien d'autres ! C'est ce que nous allons en partie voir dans le prochain cours dédié à l'optimisation d'une image Docker.