Mots-clés du Dockerfile

Ceux que nous connaissons déjà

Nous avons vu les mots-clés du Dockerfile FROM et RUN.

FROM

Le mot-clé FROM permet d'indiquer sur quelle image de base nous construirons notre propre image personnalisée.

RUN

RUN lance une ou plusieurs commandes Linux pendant la phase de construction de notre image.

ENTRYPOINT et CMD

Nous allons essayer de comprendre les mécanismes et les différences de ENTRYPOINT et de CMD.

Lancer des commandes

Pour faire simple ces deux mots-clés exécutent une commande lorsque que le conteneur est lancé. Nous devons absolument nous servir d'un tableau (par exemple : ["ps", "-a"]) pour décrire ce que nous souhaitons.

Pour bien comprendre, nous allons utiliser un exemple. Réutilisons le Dockerfile précédent et ajoutons ce que nous avons appris.

ENTRYPOINT

Commençons par ENTRYPOINT.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
ENTRYPOINT ["node", "-v"]
# Équivalent de la commande "node -v"

Construisons notre image.

Pour les images, je vous propose le format de nom suivant ubuntu_nodejs_test_<nombre>. Faire ainsi, nous permettra de nous y retrouver, car nous allons construire beaucoup d'images.

docker build -t ubuntu_nodejs_test_1 .

Maintenant, lançons le conteneur (sans l'option -it).

docker run --rm ubuntu_nodejs_test_1

Quel résultat notre machine affiche-t-il ?

v8.10.0
# La version de Node.js installé. Le numéro de la version est peut-être différent.

Pourquoi?

Parce qu'ajouter ENTRYPOINT ["node", "-v"] équivaut à demander à Docker de lancer la commande node -v après la création de notre conteneur. Et le résultat de cette commande est le numéro de la version.

CMD

Modifions le Dockerfile et remplaçons ENTRYPOINT par CMD.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
CMD ["node", "-v"]

Construisons l'image correspondante.

docker build -t ubuntu_nodejs_test_2 .

Enfin lançons un conteneur.

docker run --rm ubuntu_nodejs_test_2

Que se passe-t-il ?

v8.10.0

La même chose ! Est-ce que cela signifie que ces deux mot-clés sont identiques ? Pas tout à fait.

Différence entre ENTRYPOINT et CMD

Il y a une principale différence entre les deux.

Tapez la commande qui suit. Le résultat apparaître juste après.

docker run --rm ubuntu_nodejs_test_1 node -h
# ENTRYPOINT
v8.10.0

Dans notre commande, nous avons ajouté node -h. Pourtant, rien n'a changé au niveau du résultat.

Maintenant essayons ceci.

docker run --rm ubuntu_nodejs_test_2 node -h
# CMD
...
NODE_NO_WARNINGS             set to 1 to silence process warnings
NODE_NO_HTTP2                set to 1 to suppress the http2 module
NODE_OPTIONS                 set CLI options in the environment
                             via a space-separated list
NODE_PATH                    ':'-separated list of directories
                             prefixed to the module search path
NODE_PENDING_DEPRECATION     set to 1 to emit pending deprecation
                             warnings
NODE_REPL_HISTORY            path to the persistent REPL history
                             file
NODE_REDIRECT_WARNINGS       write warnings to path instead of
                             stderr
OPENSSL_CONF                 load OpenSSL configuration from file

Documentation can be found at https://nodejs.org/

Cette fois-ci, nous voyons la documentation de Node.js (résultat de node -h) qui apparaît.

Intéressant 🤔 ! Cela signifie que l'on ne peut pas modifier la commande node -v, lors du lancement du conteneur, avec un ENTRYPOINT. Mais c'est possible avec CMD.

Utiliser les deux mot-clés

Il est possible d'utiliser les deux mots-clés dans certains cas. Par exemple, imaginons qu'au démarrage du conteneur, nous souhaitons exécuter la commande node -v. Mais nous voulons également donner la possibilité à l'utilisateur de modifier l'option -v. Nous pourrions créer un Dockerfile comme suit.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
ENTRYPOINT ["node"]
CMD ["-v"]

Testons les choses.

docker build -t ubuntu_nodejs_test_3 .
docker run --rm ubuntu_nodejs_test_3
# Résultat : Version de Node.js
docker run --rm ubuntu_nodejs_test_3 -h
# Résultat : Documentation de Node.js

Nous utilisons CMD pour donner la possibilité à l'utilisateur de modifier l'option -v par autre chose.

LABEL

Le LABEL ajoute des métadonnées à notre image.

ℹ️ Une métadonnée est une information.

L'utilisation est simple. Il suffit d'utiliser une clé et une valeur.

Nous allons modifier notre Dockefile. Commençons par effacer les lignes correspondantes au ENTRYPOINT et au CMD. Ajoutons deux LABEL.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
LABEL description="Une image pour tester"
LABEL version="1.0.0"

Comment un utilisateur peut-il afficher les métadonnées ?

Grâce à la commande docker inspect. Pas la peine de créer un conteneur. Cette commande est utilisable directement sur une image.

Par contre, avant, il nous faudra construire l'image que nous appellerons ubuntu_nodejs_test_4.

docker build -t ubuntu_nodejs_test_4 .

Ensuite taper le commande qui suit.

docker inspect ubuntu_nodejs_test_4

Beaucoup d'informations nous sont projetées. Mais si vous cherchez bien (en scrollant), vous tomberez sur les métadonnées que nous avons créées.

"Entrypoint": null,
"OnBuild": null,
"Labels": {
  "description": "Une image pour tester",
   "version": "1.0.0"
}

ENV

L'instruction ENV permet de gérer des variables d'environnement.

Qu'est-ce qu'une variable d'environnement ? C'est une variable accessible depuis n'importe quel endroit de notre machine.

Pour configurer une variable d'environnement dans Docker, il faut utiliser une clé et une valeur.

Nous allons pratiquer pour bien comprendre. Dans notre Dockerfile, commençons par supprimer les LABEL.

Ajoutons une variable d'environnement.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
ENV PRENOM="Henrique"

Le nom de la variable est PRENOM et son contenu est Henrique.

Construisons notre image.

docker build -t ubuntu_nodejs_test_5 .

Maintenant, on va lancer un conteneur. Il faudra interagir avec celui-ci.

docker run --rm -it ubuntu_nodejs_test_5

Nous sommes désormais dans le conteneur. Pour vérifier que notre variable est bien présente, utilisez la commande printenv.

printenv PRENOM

Le résultat est le suivant.

Henrique

Cela prouve que notre système reconnaît bien notre variable d'environnement 👌.

ADD et COPY

ADD et COPY permettent de copier un dossier et/ou des fichiers qui se trouvent dans notre machine locale vers le conteneur.

J'utiliserai ADD pour les exemples, mais le principe est identique pour COPY.

La principale différence entre les deux, est que ADD nous permet également de copier des fichiers via une URL.

Pour l'exemple nous allons créer un dossier qui portera le nom test et on y mettra à l'intérieur à fichier index.html. On pourra les placer à côté du fichier Dockerfile, pour plus de facilité.

mkdir test && touch test/index.html

Nous allons modifier les fichiers index.html. Ajoutons le mot coucou

echo "coucou" > test/index.html

Voilà, nous sommes prêts 😐.

Pour la prochaine étape, nous allons supprimer la variable d'environnement que nous avons précédemment créé et ajouter la commande ADD.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
ADD /test /test_local

Dans ce Dockerfile, nous allons copier le dossier test de notre machine locale vers le dossier test_local de notre conteneur.

Pour vérifier que tout fonctionne bien, comme d'habitude, nous allons créer notre image et ensuite créer notre conteneur.

docker build -t ubuntu_nodejs_test_6 .
docker run --rm -it ubuntu_nodejs_test_6

Vérifions que notre dossier apparaît bien dans notre conteneur.

ls
bin   dev  home  lib64  mnt  proc  run   srv  test_local  usr
boot  etc  lib   media  opt  root  sbin  sys  tmp         var

Nous voyons bien test_local.

Maintenant vérifions que notre fichier index.html existe et qu'il contient le mot coucou.

cat test_local/index.html
coucou

Ça fonctionne 😃.

VOLUME

L'instruction VOLUME permet de créer automatiquement un répertoire dans la machine locale et le conteneur qui seront liés. Celui-ci sera automatiquement supprimé à la destruction du conteneur.

Faisons un test et modifions notre Dockerfile.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
VOLUME /volume_test

Construisons notre image.

docker build -t ubuntu_nodejs_test_7 .

Construisons un conteneur.

docker run --rm -it ubuntu_nodejs_test_7

Dans notre conteneur si nous tapons la commande ls, nous tombons sur notre volume créé (volume_test).

bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr  volume_test

Mais qu'en est-il coté machine locale ?

Sans sortir du conteneur (pour ne pas le supprimer), ouvrez une fenêtre de votre terminal et tapez la commande docker volume ls.

Vous allez tomber sur le volume créé, coté machine locale.

local     0e04fc8176c263ca1bf73580770c1b4e2d118027f4acbd6047db81410ff36744

Bien entendu, de votre côté, l'id sera différent.

Si vous supprimez le conteneur en le quittant, les volumes seront supprimés des deux cotés.

WORKDIR

Nous allons maintenant découvrir une nouvelle instruction : WORKDIR.

Pour bien comprendre son fonctionnement, modifions le Dockerfile et ajoutons WORKDIR. Au préalable, nous supprimerons la ligne avec VOLUME.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
WORKDIR /bin

Expliquons ce que WORKDIR va produire. En quelque sorte cette instruction est un équivalent de la commande cd qui nous permet d'accéder à un dossier.

Par conséquent, au lancement du conteneur, nous serons positionnés dans le dossier /bin. Pareillement les instructions RUN, ENTRYPOINT, COPY, ADD et CMD seront exécutées à partir de répertoire sélectionné dans WORKDIR.

Maintenant qu'elle est prête, nous allons pouvoir créer l'image.

docker build -t ubuntu_nodejs_test_8 .

Ensuite nous allons lancer un conteneur basé sur cette image.

docker run --rm -it ubuntu_nodejs_test_8

Après, tapez la commande pwd qui permet de connaître notre position.

Normalement le résultat est le suivant.

/bin

Cela signifie qu'au lancement de notre conteneur, celui-ci nous positionne automatiquement au dossier /bin et non à la racine /. C'est ainsi, car nous avons ajouté la commande WORKDIR /bin.

EXPOSE

Le mot-clé EXPOSE permet d'indiquer dans quel port le conteneur écoute. Mais ce n'est qu'une indication sous forme de documentation. Pour mapper réellement un ou plusieurs ports, il faut utiliser l'option -p.

Nous allons modifier notre Dockerfile.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
EXPOSE 8000

Dans ce fichier, nous indiquons que le port d'écoute est le 8000. C'est une information utile pour l'utilisateur.

⚠️ L'exemple ci-dessus n'est pas réel. Le conteneur n'émet pas réellement d'informations sur le port 8000. L'objectif était juste d'utiliser l'instruction EXPOSE.

ARG

On va effacer la ligne EXPOSE et utiliser l'instruction ARG.

FROM celtak/ubuntu-ping-ip
RUN apt-get update
RUN apt-get install -y nodejs
ARG prenom=henrique
RUN echo $prenom

Le mot-clé ARG dans ce Dockerfile, n'est pas utile. Mais cet exemple permet de bien comprendre son fonctionnement.

Pour faire simple, ARG est une variable disponible dans le Dockerfile (elle ne le sera pas dans le conteneur).

Elle est utilisée lors de la création de l'image. À ce moment plusieurs choses seront exécutés. La ligne RUN echo $prenom affichera le contenu de la variable prenom grâce à l'instruction ARG prenom=henrique.

Testons ce qui a été écrit.

On va créer l'image et copier ce que le terminal affiche.

docker build -t ubuntu_nodejs_test_9 .
[+] Building 0.4s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 294B                                       0.0s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                            0.0s
 => [internal] load metadata for docker.io/celtak/ubuntu-ping-ip:latest    0.0s
 => [1/4] FROM docker.io/celtak/ubuntu-ping-ip                             0.0s
 => CACHED [2/4] RUN apt-get update                                        0.0s
 => CACHED [3/4] RUN apt-get install -y nodejs                             0.0s
 => [4/4] RUN echo henrique                                                0.3s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:f6710c927511611a66b2b96bf4ede2556318a4318670a  0.0s
 => => naming to docker.io/library/ubuntu_nodejs_test_9                    0.0s

Dans les informations exposées on peut voir dans la dixième ligne en partant du haut, on peut apercevoir RUN echo henrique, ce qui confirme ce que nous avons dit précédemment.

Nous pouvons changer le contenu de la variable lors de la création de l'image.

docker build --build-arg prenom=melanie -t ubuntu_nodejs_test_10 .
[+] Building 0.4s (8/8) FINISHED
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 176B                                       0.0s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                            0.0s
 => [internal] load metadata for docker.io/celtak/ubuntu-ping-ip:latest    0.0s
 => [1/4] FROM docker.io/celtak/ubuntu-ping-ip                             0.0s
 => CACHED [2/4] RUN apt-get update                                        0.0s
 => CACHED [3/4] RUN apt-get install -y nodejs                             0.0s
 => [4/4] RUN echo melanie                                                 0.3s
 => exporting to image                                                     0.0s
 => => exporting layers                                                    0.0s
 => => writing image sha256:cf149e31809b8027a551f54c4cd9b3246ed7482f0674a  0.0s
 => => naming to docker.io/library/ubuntu_nodejs_test_10                   0.0s

Ce n'est plus RUN echo henrique qui est affiché mais RUN echo melanie.

⚠️ La documentation de Docker nous encourage à ne pas à utiliser ARG pour y insérer des données sensibles comme des mots de passes dans des variables. Car ces données seront récupérables avec un docker history <image>.

D'autres instructions

Il existe d'autres instructions utiles pour concevoir un Dockerfile. N'hésitez à aller consulter la documentation pour les découvrir.

🗑 Supprimer toutes les images

Après avoir fait tous ces exercices, nous nous retrouvons avec beaucoup d'images générées. Supprimons-les tous avec la commande qui suit.

docker image rm ubuntu_nodejs_test_1 ubuntu_nodejs_test_2 ubuntu_nodejs_test_3 ubuntu_nodejs_test_4 ubuntu_nodejs_test_5 ubuntu_nodejs_test_6 ubuntu_nodejs_test_7 ubuntu_nodejs_test_8 ubuntu_nodejs_test_9 ubuntu_nodejs_test_10