Bonnes pratiques pour les Dockerfiles

Lors de l’import d’une image dans un Dockerfile à l’aide de la directive FROM, il est important de spécifier la version de l’image importée ; sinon la dernière version (latest) est importée, ce qui peut poser des problèmes de stabilité.

Pour lancer directement le binaire d’un service, on utilisera la directive CMD avec la forme tableau ([ "commande", "arg1", "arg2" ]). Lorsque l’on a besoin d’exécuter des instructions préalables (injection de secrets, test du contact avec la base de données, etc…), on utilisera la directive ENTRYPOINT en conjonction avec CMD, toujours sous la forme tableau.

Dans ce cas, l’entrypoint recevra en argument le tableau passé à CMD.

Pour éviter d’installer deux fois un paquet, lors de l’installation de plusieurs paquets en une instruction, il convient de les installer dans l’ordre alphabétique.

Un Dockerfile est composé de différentes couches (layers). Une couche correspond à une instruction exécutée par le Dockerfile.

Lorsqu’un conteneur est lancé, il va accéder aux commandes du conteneur en remontant les différents niveaux. À la manière de l’héritage en programmation, si un conteneur cherche a appeler une commande qu’il ne connaît pas, il va remonter à sa classe mère et ainsi de suite. Or, ces accès réduisent la vitesse d’exécution de la commande. Il faut donc veiller à réduire au maximum le nombre de couches d’un conteneur et privilégier les instructions avec plusieurs commandes.

Les couches ont pour intérêt de pouvoir être partagées entre les conteneurs et utilisent un système de cache. Ainsi, la modification d’une instruction n’influencera pas les couches précédentes, qui n’auront pas besoin d’être reconstruites. Il faut donc trouver le juste milieu entre trop de couches et pas assez de couches.

En général, une couche est une sorte d’unité sémantique. L’exemple qui suit décrit l’installation de dépendances, qui n’ont pas lieu d’être séparées en trois instructions :

snippet.bash
RUN echo "deb http://packages.dotdeb.org jessie all" > /etc/apt/sources.list.d/dotdeb.list && \
wget -O- https://www.dotdeb.org/dotdeb.gpg | apt-key add - && \
apt-get update -y && apt-get install -y php7.0 php7.0-fpm php7.0-gd php7.0-xml nginx supervisor curl tar

Note:

Dans cet exemple, il n’y a qu’une seule instruction RUN, donc une seule couche est créée. Par contre, la moindre modification de cette instruction entraînera son ré-exécution complète lors de la reconstruction de l’image.

Passage de paramètres à un conteneur

Les images Docker de l’association comportent une instruction ENTRYPOINT qui permet de lancer un script à l’instanciation de l’image ; le script entrypoint.sh associé à chaque conteneur initialise dans son conteneur les variables d’environnement qui sont nécessaires.

Contrôle de la santé d'un conteneur

Afin de faciliter la supervision de l’état d’un conteneur, il convient d’inclure dans les images Docker une instruction HEALTHCHECK contenant une commande permettant de vérifier la santé du conteneur.

Options lors de l'instanciation d'images

Certaines options permettent de désactiver des fonctionnalités importantes d’isolation fournies par Docker au lancement d’un conteneur. Ces options ne doivent pas être utilisées. Elles comportent :

  • –privileged : donne toutes les capabilities et lui permet de dépasser les limites qui lui seraient normalement imposées par son cgroup ; cela expose l’hôte à des attaques de type déni de service par l’absence de limitation sur la consommation de ressources et cela donne accès au conteneur à des capabilities dont il n’a pas nécessairement besoin et qui sont vecteurs de risques
  • –security-opt : désactive l’utilisation de seccomp. seccomp est une fonctionnalité du noyau Linux permettant de limiter le nombre d’appels systèmes disponibles à un conteneur ; la désactivation de ces limiations met donc l’hôte à risque

Montage de volumes

Lors de la rédaction d’images, on peut être tenté d’exposer des volumes sur des images “temporaires”. Par exemple, un conteneur nginx auquel on va exposer le volume /var/www/html. Le problème de cette pratique est que si l’on veut créer une image dokuwiki basée sur cette image nginx, il ne sera pas possible d’écrire par dessus le volume /var/www/html. Même si au sein du Dockerfile on spécifie des commandes pour remplacer le contenu de l’image, au final, le contenu du dossier restera celui de l’image mère. Il convient également de ne pas monter de volume sensible (par exemple /etc) dans un conteneur, parce que les conteneurs peuvent par défaut modifier les volumes montés avec des privilèges root. Ainsi, il ne faut surtout pas monter la socket Docker (/var/run/docker/sock) dans un conteneur.

Présence d'informations confidentielles dans un Dockerfile ou un docker-compose.yml

Il convient de ne pas saisir d’informations confidentielles, telles que des mots de passe ou des clés SSH, dans des fichiers tels que des Dockerfiles ou docker-compose.yml ; ces fichiers sont souvent versionnés et parfois moins protégés. Pour ces raisons, il convient pour passer des paramètres tels que des mots de passe à un conteneur d’utiliser des variables d’environnement comme mentionné plus haut.

Spécifier un utilisateur lors de l'exécution de commandes

Dans un Dockerfile, une commande exécutée par une directive RUN est exécutée par le compte root du conteneur par défaut. Il convient d’exécuter aussi peu de commandes que possible avec le compte root ; pour se faire, il est possible d’utiliser la directive USER.

Réaliser des images minimales

Chaque élément d’une image peut être un facteur de vulnérabilité ; même un paquet en apparence inoffensif peut dépendre d’une librairie contenant une vulnérabilité. Pour cette raison, il convient de réaliser des images aussi légères que possibles en évitant d’installer des paquets superflus ou en supprimant les paquets installés pour un besoin temporaire (par exemple pour compiler un programme) une fois qu’ils sont obsolètes. Pour ce dernier besoin, il peut être intéressant d’avoir recours à des multi-stage builds.

Documenter les paramètres nécessaires

Chaque Dockerfile peut nécessiter le passage de paramètres tels que des mots de passe ; lorsque c’est le cas, il convient de les documenter dans le point d’entrée du conteneur et de leur y assigner une valeur par défaut.

  • technique/docker/general/tips.1602497434.txt.gz
  • de qduchemi