Création d’images

Avant de commencer

Dans la suite, nous allons utiliser le moteur de création d’images « buildkit », qui est plus intéressant (performances, fonctionnalités) que celui « par défaut » de docker.

Pour cela, vous devez exécuter :

docker buildx install

Note: avec les versions de docker récentes, il n’est plus nécessaire de faire docker buildx install.

Dockerfile

Créez un dossier « premier_dockerfile ».

Dans ce dossier écrivez un fichier hello.sh avec le contenu suivant :

#!/bin/sh
echo Bonjour tout le monde

Créez un second fichier, nommé Dockerfile avec le contenu suivant :

# syntax=docker/dockerfile:1
# L'instruction suivante indique qu'on souhaite se baser sur l'image ubuntu avec le tag 24.04
FROM ubuntu:24.04
# L'instruction suivante permet de copier hello.sh dans le dossier /bin/ de l'image en lui donnant le droit 755
COPY --chmod=755 hello.sh /bin/

Créez une image en exécutant :

docker build -t "ma_premiere_image:0.1" .

Remarquez que si vous lancez de nouveau cette commande, son exécution est bien plus rapide.

Lancez un conteneur à partir de cette image:

docker run -it ma_premiere_image:0.1 bash

Constatez que vous pouvez y lancer « hello.sh »

Layers

Exécutez la commande suivante :

docker image history ma_premiere_image:0.1
  • Dans quel ordre les couches sont elles indiquées ?

  • À quoi correspond la colonne SIZE ?

  • Remarquez la colonne « CREATED ». Relancez le build et observez de nouveau cette colonne : les temps n’ont pas changé, le nouveau build n’a en fait rien fait d’autre que de constater qu’il n’avait rien à faire.

  • Essayez de nouveau, mais cette fois ci en ajoutant l’option --no-cache à docker buid. Constatez que cette fois les images ont bien été recréées.

Essais

Créez un fichier README.txt avec quelques lignes. Ajoutez une instruction à la fin de votre Dockerfile pour que ce fichier soit copié dans /opt/.

Lancez le build avec ce nouveau Dockerfile. Constatez avec docker image history que seul le cache a été utilisé pour les anciennes couches.

Ajoutez l’instruction suivante à la fin de votre Dockerfile :

RUN <<EOF
    apt update
    apt install --no-install-recommends -y vim-tiny
    rm -rf /var/lib/apt/lists/*
EOF
  • Observez la taille des couches avec docker history.

  • Constatez que si vous lancez à nouveau le build, docker utilise son cache et que tout est exécuté très rapidement.

Les instructions Dockerfile

Jusqu’ici nous avons utilisé trois instructions différentes : FROM, COPY et RUN :

  • FROM permet de dire de quelle image on part. La bonne pratique voudra qu’on spécifie une version bien précise (ubuntu:22.04 plutôt que ubuntu ou que ubuntu:latest)

  • COPY permet de copier un fichier ou dossier dans le système de fichiers de l’image. L’option --chmod permet de préciser les droits que l’on donne aux fichiers copiés

  • RUN permet d’exécuter une commande et si celle ci impacte les fichiers, les modifications feront partie de l’image.

Il y a en tout une quinzaine d’instructions, documentées ici : https://docs.docker.com/engine/reference/builder/ et avec une dizaine on est déjà très bien. Plus que quelques unes à connaître !

ENTRYPOINT et CMD

Dans la suite, nous nous assurerons systématiquement que notre conteneur contient les deux instructions suivantes :

ENTRYPOINT ["commande", "argument1", "argument2"]
CMD ["argument_supplémentaire_par_défaut1", "argument_supplémentaire_par_défaut2"]

Lorsqu’on lance un conteneur en faisant docker run -it NOM_IMAGE, le processus qui sera lancé à l’intérieur du conteneur sera : commande argument1 argument2 argument_supplémentaire_par_défaut1 argument_supplémentaire_par_défaut2.

Lorsqu’on lance un conteneur en faisant docker run -it NOM_IMAGE argument_supp1 argumentsupp2, le processus qui sera lancé à l’intérieur du conteneur sera : commande argument1 argument2 argumentsupp1 argumentsupp2.

Par exemple, supposons qu’on ait une image essai_entrypoint construite à partir du Dockerfile suivant :

# syntax=docker/dockerfile:1
FROM alpine:3.16
ENTRYPOINT ["/bin/echo", "Bonjour"]
CMD ["tout","le","monde!"]
  • Sans essayer : quelle serait la sortie de la commande docker run essai_entrypoint ?

  • Sans essayer : quelle serait la sortie de la commande docker run essai_entrypoint Hello ?

  • Ecrivez un dockerfile ayant pour image de base alpine:3.16, avec comme ENTRYPOINT ["/bin/ls"] et comme CMD ["-lah"]. Que cela fait-il ?

  • Ecrivez un dockerfile ayant pour image de base alpine:3.16, avec comme ENTRYPOINT ["/bin/sh","-c"] et comme CMD ["sh"]. Que cela fait-il ?

On peut ne pas spécifier ENTRYPOINT ou CMD, mais le fonctionnement n’est alors pas celui décrit ci-dessus. Dans la suite nous nous assurerons de systématiquement les renseigner.

principales instuctions pour Dockerfile

Commandes

Utilisation

FROM

IMAGE

LABEL

Ajouter des métadonnées

RUN

Lancer une commande

ADD [–chown=<user>:<group>] <src> <dest>

Ajout au conteneur

COPY [–chown=<user>:<group>] <url> <dest>

Ajout au conteneur et aussi COPY --from=<image> <src><dest>

WORKDIR

Répertoire de travail

EXPOSE

Port d’écoute

ENTRYPOINT / CMD

Commande à lancer au démarrage du container

ENV

Définition de variables d’environnemment

USER

Nom d’utilisateur à utiliser

ARG

Passage arguments ex: docker build –build-arg machin=bidule

Exemple plus complet

Ecrivons une image un peu plus complète pour finir avec une petite app nodejs dans un conteneur.

Créer un répertoire de base node-serv-fic.

  • Fabriquer le fichier lireFic.js et le placer à la racine :

let http=require('http'),fs=require('fs');
http.createServer(function (req, res) {
// Ouvre et lit servHello.js
fs.readFile('hello.txt','utf8',function(err, data) {
    res.writeHead(200,{'Content-Type': 'text/plain'});
    if (err)
        res.write('Pb ouverture fichier\n');
    else// On renvoie le fichier au client
        res.write(data);
    res.end();
    });
}).listen(8200, function() {console.log('Connecte au port 8200');});
console.log('Serveur tourne sur port 8200');
  • On utilise un fichier package.json pour les dépendances :

{
    "name": "nodejs-simple",
    "version": "1.0.0",
    "description": "Envoi fichier par serveur node",
    "main": "lireFic.js",
    "keywords": [
        "node",
        "serveur",
        "fichier"
    ],
    "author": "Super Geek",
    "license": "ISC",
    "dependencies": {
        "http": "^0.0.1-security"
    }
}
  • un fichier hello.txt pour publication (contenu à votre guise !)

  • Enfin le Dockerfile :

FROM node:23.11-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY lireFic.js .
COPY hello.txt .
EXPOSE 8200
ENTRYPOINT ["node", "lireFic.js"]
CMD []

On build et on lance :

  • docker build -t node-serv-fic .

  • docker run -d --name node-serv -p 8000:8200 node-serv-fic

  • on visite http://localhost:8000

Exemple avec votre llm favori

En partant d’une page blanche, demandez à chatGPT ou autre de :

  • Créer une application python qui sert le contenu d’un fichier sur le port 3004

  • Créer un Dockerfile pour faire tourner cette application

  • Donner une commande pour faire tourner cette application sur votre machine de façon à ce qu’elle soit accessible sur le port 8000

Avant de lancer, discutez et validez (si vous avez des doutes, ne lancez rien !). Discutez les éventuelles erreurs / approximations du llm.