Bonnes pratiques Dockerfile
Conseil :
Il n'est pas toujours facile d'écrire un Dockerfile. Vous pouvez visiter le github docker-library qui regroupe tous les dockerfiles des images officielles
Dockerfile du conteneur officiel hello-world
FROM scratch
COPY hello /
CMD ["/hello"]
Conseil :
L'image de base d'un Dockerfile est très importante et nécessite d'être taguée avec une version bien précise. En effet, on doit être capable de reconstruire une image à partir de ces sources à tout moment. Les images notamment les images officielles sont mises à jour très régulièrement et il est possible qu'elle ne soit pas compatible entre elles ce qui pourrait mener à casser la construction de notre image personnalisée.
Par exemple, on construit une image FROM debian:latest
qui correspond aujourd'hui à la version stretch
. D'ici quelques mois, la version buster
de Debian va remplacer la version stretch
. Il y a de grandes chances pour que le build de notre image ne fonctionne plus à cause de ce changement.
Pour éviter ces problèmes, il est donc primordial d'indiquer une version précise dans nos Dockerfiles au niveau de l'image de base.
Fondamental :
Comme nous l'avons vu, une image Docker est composée d'un ensemble de layers correspondant chacun à une instruction dans un Dockerfile. Malgré leur faible taille, l'accumulation de ces layers peut donner lieu à des images pouvant faire jusqu'à 1Go.
Avec de telles images, on est loin de la philosophie originelle des conteneurs avec une faible utilisation de ressources.
Lorsque l'on écrit un Dockerfile, il est donc recommandé de réfléchir à ce qui est vraiment nécessaire dans le conteneur pour le faire fonctionner et ne garder que les éléments essentiels. Par exemple, il n'est pas utile de garder un éditeur de texte ou encore les paquets nécessaires à la compilation de notre programme dans l'image finale.
Complément :
En réduisant le nombre de paquets installés dans notre image, nous réduisons la surface d'attaque des conteneurs qui vont en découler. En d'autres termes, les conteneurs ne contenant pas de paquets superflus sont moins vulnérables aux attaques informatiques. Il faut garder en tête que chaque paquet contient des bug ou des failles de sécurité. Limiter le nombre de paquets limite donc le risque.
Fondamental :
Comme nous l'avons vu à chaque fois que l'on souhaite faire une modification dans une image, on doit ajouter un nouveau layer à cette image. Dans ce nouveau layers seront stockés les modifications apportées aux fichiers. Lorsqu'un conteneur fonctionne et qu'il nécessite l'accès à un fichier contenu dans le second layer, il va devoir parcourir chacun des layers de notre image pour parvenir au fichier qui l'intéresse. Limiter le nombre de layers de notre image permet donc d'accélérer le fonctionnement de nos conteneurs en limitant le passage d'une couche à une autre.
Pour cela, on essaie le plus possible de regrouper des commandes dans nos instructions. Vous l'avez peut-être remarqué, depuis le début, nous utilisons l'instruction && dans chacune de nos instructions RUN
. L'utilisation du && permet de concaténer plusieurs commandes entre elles, mais aussi de passer à la commande suivante uniquement si la commande précédente a réussie (code de sortie 0). Ainsi, on peut enchaîner plusieurs actions sur un seul et même layer.
Remarque :
Pour plus de lisibilité, il est possible d'utiliser les \
associés aux &&
ils permettent de faire un retour à la ligne non interprété par le Docker lors de la construction du Dockerfile et qui facilite grandement la lecture des Dockerfile.
RUN apt-get update && \
apt-get install -y \
unzip \
wget && \
wget https://github.com/ether/etherpad-lite/archive/1.7.0.zip && \
unzip 1.7.0.zip && \
bash etherpad-lite-1.7.0/bin/run.sh
Lors de l'installation de paquets, il est également recommandé de les installer en suivant l'ordre alphabétique, cela permet d'un coup d'oeil de voir si un paquet est installé sans avoir à parcourir tout le Dockerfile.
Fondamental :
Par défaut, c'est l'utilisateur root qui est utilisé dans les images de base. L'utilisation de cet utilisateur n'est pas recommandé lors de l'utilisation d'un conteneur.
Ce super utilisateur est en effet en mesure de tout faire dans une conteneur. Dans le cas d'une faille de sécurité, l'attaquant peut ainsi modifier notre conteneur pour injecter du code malicieux à notre insu. C'est pourquoi il est recommandé d'ajouter dans nos images un utilisateur non privilégié ayant un nombre de droits limités. Ainsi en cas de piratage, l'attaquant n'aura que très peu de possibilités dans le conteneur.
La directive USER du Dockerfile permet de changer d'utilisateur lors des instructions qui suivent sont ajout. Cela permet donc d'avoir des conteneur utilisant une utilisateur non privilégié.
FROM debian:stretch
#utilisateur root
RUN apt-get update && \
apt-get install -y figlet
USER www-data
#utilisateur www-data
CMD ["figlet", "APIUTC"]
Syntaxe :
Il peut être nécessaire de connaître le créateur d'une image afin de lui indiquer un problème ou encore lui demander d'ajouter une nouvelle fonctionnalité. Pour cela un instruction LABEL a été ajoutée dans la syntaxe du Dockerfile.
Un LABEL est une indication de la forme clé valeur qui permet de stocker des information comme la date de création de l'image ou encore son propriétaire. On l'utilise de la manière suivante :
LABEL maintainer="mon@email.com" \
creation_date="2019-01-13"
Il est ensuite possible de récupérer les labels d'une image à l'aide de la commande docker inspect
docker inspect mon-app | jq .[0].Config.Labels
{
"creation_date": "2019-01-13",
"maintainer": "mon@email.com"
}