Il y a plusieurs façons d'aboutir à une nouvelle image:
Les instructions du Dockerfile sont listés ci-dessous:
FROM # Pour choisir l'image sur laquelle on se base, toujours en premier
RUN # Permet d'exécuter une commande
CMD # Commande exécutée au démarrage du conteneur par défaut
EXPOSE # Ouvre un port
ENV # Permet d'éditer des variables d'environnement
ARG # Variables utilisées seulement le temps de la construction de l'image
COPY # Permet de copier un fichier ou répertoire de l'hôte vers l'image
ADD # Permet de copier un fichier de l'hôte ou depuis une URL vers l'image, permet également de décompresser une archive tar
LABEL # Des métadonnées utiles pour certains logiciels de gestion de conteneurs, comme rancher ou swarm, ou tout simplement pour mettre des informations sur l'image.
ENTRYPOINT # Commande exécutée au démarrage du conteneur, non modifiable, utilisée pour package une commande
VOLUME # Crée une partition spécifique
WORKDIR # Permet de choisir le répertoire de travail
USER # Choisit l'utilisateur qui lance la commande du ENTRYPOINT ou du CMD
ONBUILD # Crée un step qui sera exécuté seulement si notre image est choisie comme base
HEALTHCHECK # Permet d'ajouter une commande pour vérifier le fonctionnement de votre conteneur
STOPSIGNAL # permet de choisir le [signal](http://man7.org/linux/man-pages/man7/signal.7.html) qui sera envoyé au conteneur lorsque vous ferez un docker container stop
Pour construire l'image on utilisera la commande image build ou build:
docker image build -t [imagename][:tag] aContextFolder
build context
;
L'image Docker est construite côté serveur. Le client Docker communique au serveur le Dockerfile et les fichiers présents dans le dossier en argument de la commande docker build
: c'est le contexte. Il est possible d'exclure des fichiers du contexte grâce au fichier .dockerignore. Le détail du fonctionnement du contexte est abordé dans le wiki build context.
Dans l'exemple ci-dessous, on a écrit un fichier script.sh qui affiche régulièrement un message:
while true; do echo $(date +"%d-%m-%Y %H:%M:%S")": [APP version="$APP_VERSION"]" sleep 5 done exit 0
La variable APP_VERSION non définie dans le script sera présente dans l’environnement d'exécution. On souhaite partir d'une image de busybox existante pour y ajouter notre script
FROM busybox:1.24-glibc RUN mkdir -p /usr/local/bin COPY script.sh /usr/local/bin CMD /usr/local/bin/script.sh
Pour construire la nouvelle image:
docker image build --tag test_script:v0.1.1 .
On peut ensuite créer le conteneur à partir de notre image:
docker container run --detach --env APP_VERSION="0.1" test_script:v0.1.1
Chaque instruction modifiant la structure du système de fichier dans le Dockerfile produit un layer (calque). Un nombre important de layers conduit à une image de taille importante et peu nuire au temps d'accès de lecture/écriture. Pour éviter cela on groupe au maximum les commandes.
Ci dessous un exemple non optimisé:
# layer 1
RUN apt-get update && apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git
# layer 2
RUN cpan install Carton
# layer 3
RUN cd / && git clone https://git.framasoft.org/luc/lutim.git
# layer 4
RUN cd /lutim && carton install
# layer 5
RUN apt-get purge -y make gcc ca-certificates libssl-dev git
# layer 6
RUN apt-get autoremove --purge -y && apt-get clean
#layer 7
RUN rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
Les mêmes commandes regroupées conduiront à la création d'un seul calque:
RUN apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests perl \
ca-certificates \
shared-mime-info \
perlmagick \
make \
gcc \
ca-certificates \
libssl-dev \
git \
&& cpan install Carton \
&& cd / \
&& git clone https://git.framasoft.org/luc/lutim.git \
&& cd /lutim \
&& carton install \
&& apt-get purge -y make \
gcc \
ca-certificates \
libssl-dev \
git \
&& apt-get autoremove --purge -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /root/.cpan* /lutim/local/cache/* /lufi/utilities
# Pour l'exemple on crée un fichier de 10 Mo que l'on va ajouter à une image mkdir test cd test dd if=/dev/zero of=file.dat bs=1M count=10
Pour illustrer la problématique on va utiliser une image busybox d'une taille initiale approximative de 1 Mo
# Pour lister les images locales docker images REPOSITORY TAG IMAGE ID CREATED SIZE phobos/buildenv-ffsync v1.0 5335d8ff969e 6 weeks ago 144MB grafana/grafana 8.1.1 c6e5c4f48cf9 7 weeks ago 213MB bitnami/dokuwiki latest 1f0bc8445e43 2 months ago 387MB traefik v2.4.12 8b7f6bb63b8a 2 months ago 92MB ... busybox latest 22667f53682a 8 months ago 1.23MB
On crée un Dockerfile pour notre nouveau conteneur basé sur une image busybox à laquelle on ajoute notre fichier de données de 10 Mo:
FROM busybox:latest COPY file.dat /opt/file.dat ...
On crée l'image à partir de ce Dockerfile:
docker image build -t phobos/layer-test:v1.0 .
Si on affiche à présent les images disponibles:
docker images REPOSITORY TAG IMAGE ID CREATED SIZE phobos/layer-test v1.0 53be48f115d7 17 seconds ago 11.7MB ... busybox latest 22667f53682a 8 months ago 1.23MB
Comme on pouvait s'y attendre la taille de notre nouvelle image correspond à l'image initiale busybox à laquelle on ajoute le poids de notre fichier de données.
Le phénomène des calques apparaîtra avec la modification suivante dans le Dockerfile:
FROM busybox:latest COPY file.dat /opt/file.dat # Pour nos besoins le fichier doit appartenir à l'utilisateur 1003 RUN chown 1003:1003 /opt/file.dat
# création de l'image docker image build -t phobos/layer-test:v1.1 . # lister les images docker images REPOSITORY TAG IMAGE ID CREATED SIZE phobos/layer-test v1.1 776d428be3b6 About a minute ago 22.2MB phobos/layer-test v1.0 53be48f115d7 10 minutes ago 11.7MB ... busybox latest 22667f53682a 8 months ago 1.23MB
Ici on voit bien qu'entre les versions v1.0 et v1.1, la taille de l'image a doublé alors qu'aucun nouveau fichier n'a été ajouté à l'image. L'opération de changement de droits a conduit à un nouveau calque contenant le même fichier de 10 Mo avec les droits adéquats.
Pour éviter de créer un nouveau calque et doubler l'occupation disque on peut modifier le Dockerfile comme ci-dessous:
FROM busybox:latest COPY --chown=1003:1003 file.dat /opt/file.dat
On crée une nouvelle version avec le Dockerfile modifié
# création de l'image docker image build -t phobos/layer-test:v1.2 . # lister les images docker images REPOSITORY TAG IMAGE ID CREATED SIZE phobos/layer-test v1.2 72ca2ea7cac0 5 seconds ago 11.7MB phobos/layer-test v1.1 776d428be3b6 37 minutes ago 22.2MB phobos/layer-test v1.0 53be48f115d7 47 minutes ago 11.7MB ... busybox latest 22667f53682a 8 months ago 1.23MB
La version v1.2 obtenue avec la dernière version du Dockerfile n'a pas produit de nouveau calque, on a bien une taille de 11.7Mo pour notre image.
Limiter au maximum les processus s’exécutant en tant que root même dans le container. Utiliser exec, su-exec pour remplacer dès que possible les processus root.
Docker permet d'associer à une image des métadonnées via le mécanisme des labels.
Confère la documentation officielle Docker à propos des labels
Il existe également un projet OCI 1)
# metadata LABEL "org.opencontainers.image.authors"="contact@example.org" \ "org.opencontainers.image.url"="https://example.org/docker-images/app" \ "org.opencontainers.image.source"="https://example.org/src/app/master" \ "org.opencontainers.image.vendor"="Phobos, Inc" \ "org.opencontainers.image.description"="My Lovely App 1.5"