Workbook Docker - Ton guide de conteneurisation
Un outil pratique pour comprendre et gérer facilement les conteneurs avec Docker et Docker Compose. Il fournit les informations essentielles pour créer et stocker des images de conteneurs optimisées pour l'exécution d'applications en production.
Introduction
-
La technologie des conteneurs peut apporter portabilité, maintenabilité, évolutivité et renforcer la sécurité de votre écosystème applicatif si elle est correctement utilisée.
-
Les conteneurs regroupent les logiciels et toutes leurs dépendances dans un seul processus qui s'exécute sur une machine hôte Linux.
-
La conteneurisation est possible grâce aux fonctionnalités Linux telles que chroot, namespaces et cgroups
-
Les namespace définissent les ressources que les conteneurs peuvent voir et les cgroups les ressources qu'ils peuvent utiliser, ainsi que leur quantité.
-
Les conteneurs partagent le noyau de la machine hôte Linux, utilisent directement le matériel hôte et sont isolés les uns des autres.
-
Les environnements d'exécution de conteneurs sont des logiciels qui exécutent des conteneurs, comme Docker, Containerd et CRI-O
-
Les technologies de conteneurisation existaient bien avant Docker, mais leur adoption massive a véritablement débuté avec Docker, grâce à une approche novatrice et une interface utilisateur très conviviale.
Comment fonctionne Docker

Lorsque vous installez Docker vous disposez d'un client (la commande « docker ») et d'un démon, généralement exécuté via un service systemd. Le démon Docker s'exécute en arrière-plan et reçoit des instructions du client Docker pour télécharger les images de conteneurs depuis les registres, créer des images de conteneurs et exécuter des conteneurs. Le client et le démon ne sont pas nécessairement installés sur la même machine. La machine sur laquelle le démon s'exécute est appelée hôte Docker.
Voici les références de l'interface de ligne de commande pour le client et le démon Docker :
-
Référence de l'interface de ligne de commande du client Docker
-
Référence de l'interface de ligne de commande du démon Docker
Vous trouverez également ici la documentation de référence contenant toutes les directives de configuration pour les fichiers de configuration du client et du démon Docker :
Images de conteneurs
Le Dockerfile
Référence Dockerfile | Instructions et bonnes pratiques pour Dockerfile
-
Une image de conteneur est obligatoire pour exécuter des conteneurs.
-
Le fichier standard utilisé pour créer des images de conteneurs avec Docker est appelé un Dockerfile. Dans ce fichier, vous écrivez un ensemble d'instructions qui décrivent le contenu de l'image.
-
À la section Instructions courantes pour Dockerfile, vous découvrirez les instructions Dockerfile les plus courantes et leurs spécificités. Vous pouvez également initialiser un exemple de Dockerfile pour votre projet.
-
Une fois votre Dockerfile prêt, vous créez une image de conteneur à partir de ce fichier à l'aide d'outils comme Docker, Kaniko ou Buildah. Pour exclure un fichier spécifique du contexte de compilation, vous pouvez utiliser un fichier .dockerignore.
-
Voici également les meilleures pratiques à adopter lors de la rédaction d'instructions Dockerfile.
-
Pour des guides spécifiques à chaque langage de programmation, consultez Guides Docker spécifiques par langage de programmation :
- PHP, Python, Java, Ruby, Go, JavaScript, Se reposer, R
-
Les lignes que vous écrivez dans le Dockerfile sont traitées par les analyseurs syntaxiques de Docker. Vous pouvez personnaliser le comportement de l'analyseur syntaxique en utilisant les directives d'analyse syntaxique qui sont écrites comme un type spécial de commentaire, sous la forme « # directive=valeur », dans le Dockerfile. Elles peuvent servir, par exemple, à choisir la syntaxe du Dockerfile, à définir les caractères d'échappement ou à configurer comment les vérifications de builds sont évaluées.
Registres de conteneurs
-
Les services de registre de conteneurs permettent de stocker des images de conteneurs. Ils sont généralement accessibles à distance et servent au partage d'images de conteneurs.
-
Le Registre de conteneurs Docker est parmi les registres de conteneurs publics les plus connus. Par exemple, lorsque vous exécutez un conteneur avec Docker sans spécifier de registre particulier pour télécharger l'image, c'est celui qui est utilisé par défaut.
Création d'images de conteneur à partir d'un Dockerfile
Meilleures pratiques de construction d'image
- Accédez au dossier contenant votre fichier Dockerfile et exécutez la commande suivante. L'étiquette (ou tag) de l'image est facultative. Si aucune étiquette n'est spécifiée, « latest » sera utilisée :
# Syntax
docker build -t <image_name>[:<image_tag>] .
# Example
docker build -t myapp:0.1.0 .
- Au cas où vous auriez besoin ultérieurement de transférer l'image que vous avez créée vers un registre de conteneurs distant, '<image_name>' doit correspondre à l'URL complète de l'image dans le registre de conteneurs. Voici un exemple :
docker build -t myregistry.example.com/myapp:0.1.0 .
- Utilisez « docker build --help » pour plus d'informations. Voici également les meilleures pratiques à prendre en compte lors de la création d'images de conteneurs.
Envoi des images de conteneurs vers les registres
- Avant de transférer vos images créées localement vers des registres de conteneurs distants, assurez-vous de les avoir correctement étiquetées lors de leur création. L'URL complète de l'image doit être au format suivant :
# Registry image URL
<registry_url>/<image_name>:<image_tag>
# Examples
myregistry.example.com/tools/myapp:0.1.0
myregistry.example.com/superapp:0.1.0
- Une fois l'image correctement étiquetée, authentifiez-vous auprès du registre de conteneurs (si nécessaire) à l'aide de la commande suivante :
# Syntax
docker login <registry_domain_name>
# Example
docker login myregistry.example.com
- Utilisez ensuite la commande suivante pour insérer l'image dans le registre :
# Syntax
docker push <registry_image_url>
# Examples
docker push myregistry.example.com/tools/myapp:0.1.0
docker push myregistry.example.com/superapp:0.1.0
Builders
-
Docker utilise sous le capot des builders lors de la création d'images de conteneurs. Le 'builder' dans Docker s'appelle BuildKit. Il est intégré au démon Docker et est utilisé par défaut pour créer des images.
-
Buildx peut être utilisé pour étendre les fonctionnalités de création d'images (build) avec BuildKit. Il existe d'autres drivers de build que vous pouvez configurer via buildx lors de la création de nouveaux 'builders'.
Voici des exemples de commandes pour gérer les 'builders' Docker avec buildx :
- Lister les 'builders' :
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
default* docker
\_ default \_ default running v0.21.0 linux/amd64 (+4), linux/386
- Créez un nouveau 'builder' utilisant le driver « docker-container ». Celui-ci exécutera un conteneur BuildKit pour chaque construction d'image au lieu d'utiliser l'instance BuildKit intégrée au démon Docker :
# Create the new builder
$ docker buildx create \
--name container-builder \
--driver docker-container
# Verify
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
container-builder docker-container
\_ container-builder0 \_ unix:///var/run/docker.sock inactive
default* docker
\_ default \_ default running v0.21.0 linux/amd64 (+4), linux/386
- Configurer le nouveau 'builder' nommé « container-builder » comme 'builder' par défaut. Vous pouvez également sélectionner le 'builder' à utiliser en indiquant son nom au travers de la variable d'environnement BUILDX_BUILDER ou en utilisant l'option '--builder' lors de l'exécution de la commande 'docker build' ou 'docker buildx build'.
# Set the newly created builder as the default one
$ docker buildx use container-builder
# Verify
$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
container-builder* docker-container
\_ container-builder0 \_ unix:///var/run/docker.sock inactive
default docker
\_ default \_ default running v0.21.0 linux/amd64 (+4), linux/386
# The asterix (*) indicates
# the default builder
Layers des images / caching
Chaque instruction à l'intérieur d'un Dockerfile créera une nouvelle couche au-dessus de l'image actuelle. Comprendre le fonctionnement des caches de construction d'images Docker peut s'avérer utile pour accélérer la création de vos images de conteneurs.
Voici le lien pour en savoir plus sur le cache lors de la construction d'images Docker.
Instructions courantes pour Dockerfile
Référence Dockerfile | Instructions et bonnes pratiques pour Dockerfile
FROM
Documentation de l'instruction
L'instruction FROM peut être utilisée pour initialiser une nouvelle étape (stage) de construction/build et définir l'image de base pour les instructions suivantes.
Si une autre directive FROM est utilisée dans le même Dockerfile, cela créera une autre image et un build multi-étapes. Les ressources d'images des étapes de build précédentes pourraient ensuite être réutilisées dans les étapes de build suivantes.
# Syntax
FROM [--platform=<platform>] <image>[:<tag> | @digest] [AS <name>]
- La plateforme se présente sous la forme « os/arch », par exemple :
- 'linux/amd64' ou 'windows/arm64'
- Le nom donné au l'étape/stage pourrait alors être utilisé comme suit :
# Create a new build stage using the previous stage image as the base image
FROM <name>
# Copy a file from a specific named build stage into this current stage image
COPY --from=<name> <src> ... <dest>
# Inject files or directories from a specific named
# build stage into this current stage image
RUN --mount=type=bind,from=<name>,target=<target_path>
COPY
Documentation de l'instruction
La commande COPY peut être utilisée pour copier des fichiers à partir du contexte de build ou à partir d'une étape d'un build multi-étapes vers le système de fichiers de l'image. Les fichiers copiés resteront dans l'image finale. Pour ajouter temporairement des fichiers du contexte de build, uniquement pour une instruction RUN par exemple, utilisez plutôt un point de montage de type 'bind mounts' comme suit :
RUN --mount=type=bind,source=requirements.txt,target=/tmp/requirements.txt \
pip install --requirement /tmp/requirements.txt
Les montages de type 'bind mounts' sont plus efficaces que COPY. Voici des exemples utilisant COPY:
# Copy from the buid context
COPY --chown=www-data:www-data --chmod=755 conf/php-config.ini "$PHP_INI_DIR/conf.d/"
# Copy from a build stage (in multi-stage build)
COPY --chown=www-data:www-data --chmod=755 --from=replace conf/nginx.conf $NGINX_CONF_PATH
ADD
Documentation de l'instruction
Outre la copie de fichiers du contexte de build vers le système de fichiers de l'image, ADD permet également de télécharger des fichiers depuis des URL HTTPS et Git distantes, de vérifier les sommes de contrôle de fichiers, et bien plus encore. Elle peut aussi extraire automatiquement les fichiers tar lors de leur ajout au système de fichiers de l'image depuis le contexte de build.
Voici des exemples d'utilisation de ADD :
# Add a file from a remote HTTPS URL into the image
ADD https://example.com/myfile.zip /files/
# Add files from a remote Git repository into the image
ADD git@my.repo.example:web/app.git /app
ARG
Documentation de l'instruction
L'instruction ARG peut être utilisée pour les variables de construction. Ces variables sont utilisées uniquement lors du processus de création de l'image et ne sont pas conservées dans l'image finale. De plus, l'instruction ARG n'est pas adaptée au stockage d'informations sensibles (clés API, mots de passe, etc.), car elles seraient visibles lors de l'utilisation de la commande « docker history ».
Voici des exemples d'utilisation d'ARG :
# Declare one or many build time variables
ARG myvar1=myvalue1 MYVAR2=myvalue2 MYVAR3
Durant le processus de build, vous pouvez également définir ou remplacer les variables de build précédemment déclarées comme suit :
$ docker build -t myapp:1.0.1 \
--build-arg myvar1=myvalue2 \
--build-arg MYVAR3=myvalue3
Si vous devez injecter des données sensibles dans le contexte de build pour exécuter une commande spécifique, utilisez RUN --mount=type=secret.
Voici des exemples d'injection de données secrètes dans le contexte de build :
- injecter un fichier contenant des données secrètes
- injecter une variable d'environnement contenant des données secrètes
Voici des exemples pour vous aider à comprendre la portée des variables de compilation :
# These variables have a global scope. They will be available for all
# the FROM instructions but not inside the build stages themselves.
ARG IMAGE_STAGE1=ubuntu
ARG IMAGE_STAGE2=debian
FROM ${IMAGE_STAGE1} AS stage1 # variable accessible here
# Should be redeclared inside this stage in order to use it
ARG IMAGE_STAGE1
RUN echo "Hello from ${IMAGE_STAGE1}"
FROM ${IMAGE_STAGE2} AS stage2 # variable accessible here
# Should be redeclared inside this stage in order to use it
ARG IMAGE_STAGE2
RUN echo "Hello from ${IMAGE_STAGE2}"
ENV
Documentation de l'instruction
L'instruction ENV peut être utilisée pour définir des variables d'environnement qui seront disponibles pour les instructions suivantes dans le Dockerfile (comme ARG), mais, contrairement à ARG, les variables seront également disponibles dans l'environnement des conteneurs créés à partir de l'image résultante.
# Syntax
ENV <varname>=<value> [<key>=<value>...]
Ces variables peuvent être remplacées lors du lancement de conteneurs avec la commande « docker run », en utilisant les options « --env varname=value » ou « -E varname=value ».
RUN
Documentation de l'instruction
- L'instruction RUN est utilisée pour exécuter les commandes nécessaires à la construction de l'image.
- La commande exécutée crée une nouvelle couche au-dessus de l'image actuelle.
- Cette couche est ensuite utilisée dans l'étape suivante du Dockerfile.
Voici la syntaxe :
# Shell form:
RUN [OPTIONS] <command> ...
# Exec form:
RUN [OPTIONS] [ "<command>", ... ]
Voici quelques exemples d'utilisation :
# Using heredocs
RUN <<EOF
apt update
apt install -y curl zip
EOF
# Using escape
RUN apt update && \
apt install -y \
curl \
zip
ENTRYPOINT et CMD
Ces deux instructions permettent d'exécuter la première commande du conteneur. Cette commande s'exécutera au démarrage du conteneur et créera un processus dont l'identifiant de processus (PID) est 1. Voici des exemples pour vous aider à comprendre ces instructions.
Créons l'image example1, exécutons un conteneur à l'aide de cette image et voyons ce qui se passe :
# Example1 Dockerfile content
FROM ubuntu
CMD ["echo", "hello"]
# Build the example1 image
$ docker build -t example1 .
[+] Building 0.2s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 198B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:latest 0.1s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> CACHED [example1 1/1] FROM docker.io/library/ubuntu:latest@sha256:66460d557b25769b102175144d538d88219c077c678a49af4afca6fbfc1b5252 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:36423e8273020c1fc9186f698397a8d4524b46ecd0b638c4a3315ce1028ffcd6 0.0s
=> => naming to docker.io/library/example1 0.0s
# Launch a container using that image
$ docker run example1
hello
Le conteneur s'exécute, affiche « hello » comme prévu et s'arrête car la commande a terminé son exécution. Si nous relançons le conteneur et spécifions une autre commande à exécuter, cette dernière remplacera celle définie dans l'instruction CMD du Dockerfile.
$ docker run example1 cat /etc/issue
Ubuntu 24.04.3 LTS \n \l
À présent, créons et exécutons l'image example2 en utilisant ENTRYPOINT au lieu de CMD et observons la différence :
# Example2 Dockerfile content
FROM ubuntu
ENTRYPOINT ["echo", "hello"]
# Build the example2 image
$ docker build -t example2 .
[+] Building 0.2s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 198B
(...)
# Launch a container using that image
$ docker run example2
hello
Le conteneur s'exécute à nouveau, affiche « hello » comme prévu et s'arrête une fois la commande terminée. Si nous relançons le conteneur et spécifions une autre commande, cette dernière ne remplacera pas celle définie dans l'instruction ENTRYPOINT du Dockerfile. Elle sera simplement ajoutée comme argument à la commande ENTRYPOINT.
$ docker run example2 cat /etc/issue
hello cat /etc/issue
Le conteneur example2 exécute la commande « echo hello cat /etc/issue » au lieu de remplacer la commande « echo hello » par « cat /etc/issue », comme dans l'exemple 1. Il est toutefois possible de réinitialiser l'ENTRYPOINT lors du lancement du conteneur et d'exécuter une nouvelle commande si nécessaire.
$ docker run --entrypoint="" example2 cat /etc/issue
Ubuntu 24.04.3 LTS \n \l
En réinitialisant ENTRYPOINT, nous pouvons exécuter la nouvelle commande 'cat /etc/issue' comme dans l'exemple 1.
Si vous envisagez de conteneuriser un programme CLI, par exemple, vous devriez privilégier ENTRYPOINT à CMD car cela offrira une expérience utilisateur quasiment identique :
# Running a containerized version of mycli program
docker run mycli --help
docker run mycli <ARGS>
Vous exécutez l'image et utilisez directement les options et arguments du programme CLI que vous souhaitez, sans avoir besoin de relancer le programme.
Un autre exemple que je souhaite vous montrer est l'effet de l'utilisation combinée de ENTRYPOINT et CMD dans le Dockerfile :
# Example3 Dockerfile content
FROM ubuntu
ENTRYPOINT ["echo"]
CMD ["hello"]
# Build the example3 image
docker build -t example3 .
[+] Building 3.3s (5/5) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 83B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:latest 0.6s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/1] FROM docker.io/library/ubuntu:latest@sha256:66460d557b25769b102175144d538d88219c077c678a49af4afca6fbfc1b5252 2.5s
=> => resolve docker.io/library/ubuntu:latest@sha256:66460d557b25769b102175144d538d88219c077c678a49af4afca6fbfc1b5252 0.0s
=> => sha256:d22e4fb389065efa4a61bb36416768698ef6d955fe8a7e0cdb3cd6de80fa7eec 424B / 424B 0.0s
=> => sha256:97bed23a34971024aa8d254abbe67b7168772340d1f494034773bc464e8dd5b6 2.30kB / 2.30kB 0.0s
=> => sha256:4b3ffd8ccb5201a0fc03585952effb4ed2d1ea5e704d2e7330212fb8b16c86a3 29.72MB / 29.72MB 0.5s
=> => sha256:66460d557b25769b102175144d538d88219c077c678a49af4afca6fbfc1b5252 6.69kB / 6.69kB 0.0s
=> => extracting sha256:4b3ffd8ccb5201a0fc03585952effb4ed2d1ea5e704d2e7330212fb8b16c86a3 1.8s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:16805ffadd43c7f568fb26de593c3baf2b4c756c0be65af397c7ec5a2581681c 0.0s
=> => naming to docker.io/library/example3
# Launch a container using that image
$ docker run example3
hello
Dans cet exemple, l'argument de la commande « echo », qui est « hello », ne fait pas partie de la commande ENTRYPOINT, mais est défini dans une autre commande CMD. Son utilisation de cette manière définit « hello » comme argument par défaut de la commande définie avec l'instruction ENTRYPOINT. Ainsi, lorsque nous exécutons le conteneur example3, nous obtenons le même résultat qu'auparavant : il affiche « hello ». Les utilisateurs de cette image pourront facilement remplacer l'argument par défaut comme indiqué ci-dessous :
$ docker run example3 Welcome to hackerstack.org
Welcome to hackerstack.org
Enfin, je souhaite vous montrer comment l'état de la première commande du conteneur et la manière dont elle est exécutée (au premier plan ou en arrière-plan) influencent l'état du conteneur lui-même.
Voici le contenu du Dockerfile que nous utiliserons pour tous les exemples :
FROM ubuntu
COPY script.sh /opt
ENTRYPOINT ["/opt/script.sh"]
Nous ne modifierons que le contenu du fichier 'script.sh' d'un exemple à l'autre.
-
La première commande du conteneur s'est terminée avec succès. L'état du conteneur indiqué dans la colonne « STATUS » de la commande « docker ps » est :
- 'Sorti (0)'
# Content of script.sh
#!/bin/bash
echo "Exiting with exit code 0"
exit 0
# After building example image with that script
$ docker run example
Exiting with exit code 0
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
fd33afbaf973 example "/opt/script.sh" 8 seconds ago Exited (0) 8 seconds ago clever_northcutt
-
La première commande du conteneur s'est terminée sans succès. L'état du conteneur indiqué dans la colonne « STATUS » de la commande « docker ps » est :
- 'Sorti (1)'
# Content of script.sh
#!/bin/bash
echo "Exiting with exit code 1"
exit 1
# After building example image with that script
$ docker run example
Exiting with exit code 1
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84fc7e7854ed example "/opt/script.sh" 4 seconds ago Exited (1) 3 seconds ago angry_lichterman
-
La première commande du conteneur continue de s'exécuter au premier plan. L'état du conteneur indiqué dans la colonne « STATUS » de la commande « docker ps » est :
- « Up » (le système est en marche) suivi de la durée de fonctionnement
# Content of script.sh
#!/bin/bash
echo "Keeping a command running in foreground"
sleep 300 # 300 secondes
# After building example image with that script
$ docker run example
Keeping a command running in foreground
# the command docker command also run in the
# foreground and block shell interaction
# To run the docker command in background, we can
# use the -d flag as follows
$ docker run -d example
129bab01dc1a1b3529aa620943d432fcd7ce7c5b882cc0575afb01f5bb9f8e05
# Show running containers
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
129bab01dc1a example "/opt/script.sh" 2 minutes ago Up 2 minutes admiring_shamir
-
La première commande du conteneur continue de s'exécuter, mais cette fois-ci en arrière-plan. L'état du conteneur indiqué dans la colonne « STATUS » de la commande « docker ps » est le suivant :
- 'Sorti (0)'
#!/bin/bash
echo "Keeping a command running in foreground"
sleep 300 &
$ docker run -d example
638bb58dd066da0cc43921bf0739eb9763f1ff14e46b85d06eb2fc6f66f5ea64
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
638bb58dd066 example "/opt/script.sh" 7 seconds ago Exited (0) 6 seconds ago crazy_nash
- Dans tous les exemples précédents, les messages texte envoyés à la sortie standard (stdout) ou à la sortie d'erreur standard (stderr) avec la commande « echo » ou autre, seront visibles dans les journaux du conteneur :
$ docker logs 638bb58dd066
Keeping a command running in foreground
Pour conclure cette section, voici quelques règles importantes à garder à l'esprit lorsque vous travaillez avec ENTRYPOINT et CMD :
-
Au moins une commande ENTRYPOINT ou CMD est obligatoire dans un Dockerfile.
-
S'il existe plusieurs instructions CMD, seule la dernière sera exécutée.
-
ENTRYPOINT permet de définir un exécutable par défaut pour le conteneur. Toute commande exécutée via ce conteneur passera en argument de l'exécutable par défaut défini par ENTRYPOINT.
-
L'instruction ENTRYPOINT peut être suivie d'une instruction CMD afin de définir les arguments par défaut de l'exécutable défini avec ENTRYPOINT. L'utilisateur peut remplacer ces arguments par défaut en en fournissant de nouveaux lors du lancement du conteneur.
-
L'état d'un conteneur est lié à l'état de son premier processus : le processus avec le PID 1.
-
S'il n'y a pas d'instruction ENTRYPOINT, le processus avec PID 1 à l'intérieur du conteneur sera créé à partir de l'exécutable spécifié dans la dernière instruction CMD.
-
S'il existe une instruction ENTRYPOINT, le processus avec l'identifiant de processus 1 à l'intérieur du conteneur sera créé à partir de l'exécutable spécifié dans l'instruction ENTRYPOINT.
-
Les journaux que nous obtiendrons du conteneur (avec 'docker logs' ou autre) proviendront de la sortie standard (stdout) et de la sortie d'erreur standard (stderr) du conteneur.
-
La notion de processus au premier plan du conteneur est cruciale, car elle détermine si ce dernier continue de fonctionner ou non. Si le processus du conteneur avec l'identifiant de processus 1 (créé avec ENTRYPOINT ou CMD) est au premier plan, le conteneur est en cours d'exécution ; sinon, il est créé puis arrêté.
Une méthode courante pour exécuter des services démons dans des conteneurs consiste à utiliser un système de contrôle de processus comme supervisord comme expliqué ici.
WORKDIR
Documentation de l'instruction
L'instruction WORKDIR permet de définir le répertoire de travail des commandes telles que RUN, COPY, CMD, ENTRYPOINT et ADD qui suivent dans le Dockerfile. Le répertoire spécifié sera créé s'il n'existe pas. Exemple d'utilisation :
WORKDIR /app
USER
Documentation de l'instruction
L'instruction USER permet de définir l'utilisateur (et éventuellement le groupe) à utiliser pour les instructions RUN qui suivent dans le Dockerfile.
Ce sera également l'utilisateur par défaut à l'intérieur du conteneur, et il sera utilisé pour exécuter les scripts/commandes spécifiés dans le Dockerfile avec les instructions ENTRYPOINT ou CMD qui suivent la directive. Exemples d'utilisation :
# Using username:groupname
# groupname is optional
USER gmkziz:admin
# Using UID:GID
# GID is optional
USER 1001:900
EXPOSE
Documentation de l'instruction
L'instruction EXPOSE peut servir à indiquer un port spécifique sur lequel le conteneur écoutera. Elle ne publie pas réellement le port.
Voici un exemple d'utilisation :
(...)
EXPOSE <port>
Pour publier le port exposé sur l'hôte lors de l'exécution du conteneur, vous pouvez utiliser l'option '-P <host_port>:<container_port>' de la commande 'docker run'.
Exemples et conseils concernant les Dockerfiles
Remplacer les variables des fichiers de configuration par des variables d'environnement
Cet exemple exploite le build multi-stage Docker afin d'optimiser la taille des images.
Imaginez que vous deviez injecter un fichier de configuration dans l'image, dont le contenu comprend une variable déclarée dans le Dockerfile pour être utilisée par différentes étapes de construction.
ARG APP_BASE_DIR=/app
FROM myimage
ARG APP_BASE_DIR
COPY conf/myfile.conf /etc/nginx/http.d/default.conf
(...)
La variable APP_BASE_DIR est déclarée une seule fois dans le Dockerfile et contient le chemin d'accès au répertoire de base de l'application.
Le contenu du fichier de configuration appelé « myfile.conf » que nous copions dans le conteneur doit également contenir la valeur de cette même variable APP_BASE_DIR.
Pour ce faire, nous pouvons créer localement un modèle du fichier de configuration. Voici un exemple de contenu :
# File: myfile.conf.tpl
(...)
root {{ env "APP_BASE_DIR" }};
(...)
Ensuite, dans le Dockerfile, nous créons une étape temporaire pour effectuer le remplacement de la variable à l'aide de l'outil go-replace :
(...)
FROM webdevops/go-replace AS replace
ARG APP_BASE_DIR
WORKDIR /conf
RUN --mount=type=bind,source=./myfile.conf.tpl,target=myfile.conf.tpl,rw \
--mount=type=cache,target=/tmp/cache \
go-replace --mode=template myfile.conf.tpl:myfile.conf
Cela créera le fichier « /conf/myfile.conf » dans cette étape en remplaçant :
{{ env "APP_BASE_DIR" }}par '/app'.
Vous pourrez ensuite copier ce fichier de configuration final dans l'image aux étapes suivantes, comme suit :
COPY --from=replace conf/myfile.conf /etc/nginx/http.d/default.conf
Exécution de plusieurs services dans le même conteneur à l'aide de supervisord
Une méthode courante pour exécuter plusieurs services dans des conteneurs de manière fiable consiste à utiliser un système de contrôle de processus comme supervisord qui s'exécutera en tant que ENTRYPOINT (PID 1) et lancera les services requis (par exemple : nginx et php-fpm).
Voici un exemple d'extrait de Dockerfile utilisant supervisord pour démarrer plusieurs services via un script ENTRYPOINT :
(...)
RUN apk update && apk add --no-cache supervisor
COPY --chown=www-data:www-data --chmod=755 conf/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY --chown=www-data:www-data --chmod=755 --from=replace conf/entrypoint.sh /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
Voici un exemple de contenu pour le script ENTRYPOINT :
# file: /usr/local/bin/entrypoint.sh
#!/bin/sh
set -e
# Start supervisord and pass it the configuration file
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
Dans un conteneur exécutant php-fpm et nginx par exemple, il est possible de le configurer supervisord pour lancer php-fpm en arrière-plan et nginx au premier plan. De plus, il peut être configuré pour éviter l'enregistrement de ses journaux dans le système de fichiers du conteneur.
# File: /etc/supervisor/conf.d/supervisord.conf
[supervisord]
nodaemon=true
logfile=/dev/null
logfile_maxbytes=0
[program:php-fpm]
command=php-fpm
autostart=true
autorestart=true
priority=5
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:nginx]
command=nginx -g 'daemon off;'
autostart=true
autorestart=true
priority=10
stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
Avec cette configuration, le conteneur sera considéré comme en cours d'exécution tant que nginx est actif, et les journaux nginx envoyés à la sortie standard s'afficheront lors de la consultation des journaux du conteneur avec la commande « docker logs », par exemple. Voici un exemple de fichier de configuration pour nginx :
upstream php {
server localhost:9000;
}
server {
listen 80;
server_name localhost;
root /app;
server_tokens off;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php(/|$) {
include fastcgi_params;
fastcgi_pass php;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
error_log /dev/stdout;
access_log /dev/stderr;
}
}
Plus important encore supervisord redémarrera automatiquement les processus php-fpm ou nginx en cas de défaillance.
Utilisation de builds multi-étapes
Voici un exemple avec une application PHP où les dépendances de développement et de production sont intégrées dans des étapes différentes.
Les résultats de ces étapes sont ensuite réutilisés lors des phases de développement et de production, à partir desquelles l'image de développement ou de production de l'application sera construite.
L'environnement de développement ou de production sera créé à partir d'un environnement commun appelé base contenant les éléments qui doivent être disponibles pour le développement et la production (php et extensions php, code source de l'application, etc.).
Cela nous permet de construire indépendamment des images de développement et de production, chaque image ne contenant que les dépendances dont elle a besoin (image plus petite).
Voici l'étape des dépendances de développement :
FROM composer:lts AS dev-deps
WORKDIR /app
RUN --mount=type=bind,source=./src/composer.lock,target=composer.lock \
--mount=type=bind,source=./src/composer.json,target=composer.json \
--mount=type=cache,target=/tmp/cache \
composer install --ansi --no-scripts --no-progress
Et l'étape des dépendances de production :
FROM composer:lts AS prod-deps
WORKDIR /app
RUN --mount=type=bind,source=./src/composer.lock,target=composer.lock \
--mount=type=bind,source=./src/composer.json,target=composer.json \
--mount=type=cache,target=/tmp/cache \
composer install --ansi --no-scripts --no-progress --no-dev
Les deux étapes précédentes créeront un répertoire « vendor » dans « /app » contenant les fichiers de dépendances nécessaires. Nous injecterons ce répertoire « vendor » dans les étapes finales des images de développement et de production.
Voici maintenant l'étape de base commune aux images des environnements de développement et de production :
(...)
FROM php:8.4.14-fpm-alpine3.21 AS base
ARG APP_BASE_DIR
RUN apk update && apk add --no-cache \
build-base \
git \
(...)
RUN docker-php-ext-install \
gd \
xml \
(...)
COPY --chown=www-data:www-data --chmod=755 ./src "$APP_BASE_DIR"
(...)
WORKDIR $APP_BASE_DIR
Et enfin, l'étape qui servira à construire l'image de développement :
FROM base AS developement
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
COPY --chown=www-data:www-data --chmod=755 --from=dev-deps app/vendor /app/includes/vendor
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 80
Et celle pour l'image de production :
FROM base AS production
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
COPY --chown=www-data:www-data --chmod=755 --from=prod-deps app/vendor /app/includes/vendor
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 80
Gestion des conteneurs avec Docker
Lancement des conteneurs
- Pour plus de détails sur la syntaxe et les options, utilisez :
docker run --help
- Exécutez un conteneur utilisant l'image busybox pour tester la connectivité à 'google.com' à l'aide de la commande 'nc' :
docker run busybox nc -vz google.fr 80
- Assurez-vous que le conteneur est supprimé de la liste des conteneurs (affichée avec « docker ps ») après l'exécution :
docker run --rm busybox nc -vz google.fr 80
- Donnez un nom au conteneur :
docker run --name debug busybox nc -vz google.fr 80
- Transmettez des variables d'environnement au conteneur :
$ docker run -e MYVAR=VALUE busybox env | grep MYVAR
MYVAR=VALUE
- Monter un volume dans le conteneur :
$ docker run -v "$PWD/Test:/test" busybox ls /test
hello
- Lancez un conteneur et accédez à un shell à l'intérieur. Pour cela, exécutez simplement la commande shell correspondante (bash, sh, etc.) lors du lancement du conteneur et utilisez l'option « -it » :
# -i, --interactive: Keep STDIN open even if not attached
# -t, --tty: Allocate a pseudo-TTY
$ docker run -it busybox sh
/ # env
HOSTNAME=841da4299bb8
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
Exécution de commandes à l'intérieur de conteneurs en cours d'exécution
- Pour plus de détails sur la syntaxe et les options, utilisez :
docker exec --help
- Exécutez la commande « ls » à l'intérieur d'un conteneur en cours d'exécution nommé myapp :
docker exec myapp ls
- Obtenez un shell à l'intérieur d'un conteneur en cours d'exécution appelé myapp :
# Sh
docker exec -it myapp sh
# Bash
docker exec -it myapp bash
Lister les conteneurs et les images
- Pour plus de détails sur la syntaxe et les options de la commande, utilisez :
# Help on listing containers
docker ps --help
# Help on listing container images
docker images --help
- Lister les conteneurs en cours d'exécution
docker ps
- Lister tous les conteneurs (en cours d'exécution ou non) :
docker ps -a
- Lister les images de conteneurs
docker images
Inspection des conteneurs
- Pour plus de détails sur la syntaxe et les options, utilisez :
docker inspect --help
- Inspectez le conteneur nommé myapp :
docker inspect myapp
La commande « docker inspect » ne se limite pas aux conteneurs et peut également être utilisée pour d'autres objets Docker tels que les images, les volumes, etc.
# Inspecting a docker image
docker inspect myimage
# Inspecting a docker volume
docker inspect myvolume
Consultation des journaux des conteneurs
- Pour plus de détails sur la syntaxe et les options, utilisez :
docker logs --help
- Afficher toutes les lignes de journal disponibles pour le conteneur nommé myapp :
docker logs myapp
- Afficher uniquement les 10 dernières lignes de journalisation du conteneur nommé myapp :
docker logs -n 10 myapp
- Suivez le flux de sortie des journaux pour le conteneur nommé myapp en commençant par les 10 dernières lignes de sortie :
docker logs -n 10 -f myapp
Limiter la taille des fichiers journaux de sortie des conteneurs
Par défaut, la sortie de la commande docker logs est extraite d'un fichier stocké à l'emplacement suivant :
/var/lib/docker/containers/<container_id>/<container_id>-json.log
Pour limiter la taille de ce fichier sur le système de fichiers de l'hôte/machine Docker, les directives suivantes peuvent être ajoutées dans le fichier de configuration du démon Docker :
# File: /etc/docker/daemon.json
{
(...)
"log-opts": {
"max-size": "<max_size_of_log_files>",
"max-file": "<max_number_of_log_files>",
}
(...)
}
Voir Configurer le driver par défaut pour les logs Docker pour plus d'informations.
Affichage de la consommation des ressources des conteneurs
- Pour plus de détails sur la syntaxe et les options, utilisez :
docker stats --help
- Afficher un flux en direct des statistiques d'utilisation des ressources des conteneurs :
docker stats
Nettoyage des informations système et des données inutilisées
# Info about the Docker client (version, mode, plugins, etc) and the Docker daemon
docker system info
# Info about data size used by images,
# containers, volumes, build caches
docker system df
# Remove unused data: images, containers,
# volumes, build caches
docker system prune [--all, --force]
# Cleanup only unused images, containers, volumes, etc
docker image|container|volume prune [--force]
Utilisez un proxy pour télécharger les images Docker
Vous devez configurer le proxy au niveau du démon Docker. Ci-dessous une façon de faire si vous utilisez systemd comme gestionnaire de services système.
Créez le fichier de configuration suivant :
# File: /etc/systemd/system/docker.service.d/docker-service-override.conf
[Service]
Environment="https_proxy=<proxy_ip>:<proxy_port>"
Ensuite, informez le démon systemd de la modification du fichier de configuration du service Docker en exécutant la commande suivante :
systemctl daemon-reload
Ensuite, redémarrez le démon Docker à l'aide de la commande suivante :
systemctl restart docker
Configurer un proxy pour tous les conteneurs Docker
Cela permettra à tous vos conteneurs Docker en cours d'exécution d'utiliser le proxy configuré pour leurs requêtes HTTP/HTTPS.
Ajoutez les éléments suivants au fichier de configuration du client Docker :
# File: $HOME/.docker/config.json
{
"proxies": {
"default": {
"httpProxy": "<proxy_ip>:<proxy_port>",
"httpsProxy": "<proxy_ip>:<proxy_port>"
}
}
}
Gestion des conteneurs avec Docker Compose
Qu'est-ce que Docker Compose ?
Docker Compose est un outil supplémentaire qui permet de gérer les conteneurs Docker de manière déclarative.
Au lieu d'exécuter des commandes pour lancer individuellement les conteneurs Docker, vous créez un fichier de configuration dans lequel vous déclarez les conteneurs que vous souhaitez exécuter, avec les paramètres (volumes, variables d'environnement, etc.) avec lesquels vous souhaitez qu'ils s'exécutent.
Pour installer Docker Compose sous Linux, consultez cette documentation. Il est disponible sous forme de plugin pour Docker et peut être exécuté via la commande « docker compose ».
Création d'un fichier Docker Compose
Référence du fichier Docker Compose
La section de niveau supérieur la plus couramment utilisée dans un fichier Docker Compose est service. C'est là que vous déclarez vos conteneurs et leurs paramètres. Voici un exemple :
services:
myapp-frontend:
image: myapp
container_name: myapp-frontend
volumes:
- source: $PWD/.env
type: bind
read_only: true
target: /app/config/.env
environment:
DEBUG: false
DATABASE_HOST: myapp-database
ports:
- "8000:80"
myapp-database:
image: mysql:8.0.43-bookworm
container_name: myapp-database
environment:
MYSQL_ROOT_PASSWORD: "mysuperpass"
MYSQL_DATABASE: "myapp"
volumes:
- source: /var/tmp/myapp-db-data
type: bind
read_only: false
target: /var/lib/mysql
depends_on:
- myapp-frontend
À l'intérieur de la section principale service, voici la documentation d'autres directives que je souhaite mettre en avant :
-
deploy: configurer les demandes et les limites de ressources, le nombre de réplicas, etc.
-
volumes: monter des volumes à l'intérieur de conteneurs à l'aide de 'bind mounts', de volumes Docker, de données provenant d'images de conteneurs, etc.
-
bilan de santéConfigurez les contrôles d'intégrité de vos conteneurs.
-
dns: configurez les adresses des serveurs DNS à utiliser pour la résolution DNS à l'intérieur de vos conteneurs.
-
depends_on: déclarez les dépendances entre vos services docker compose.
-
build: créer des images de conteneurs au lancement, à partir de répertoires de code source locaux.
-
develop :utile pour le développement local afin de maintenir le conteneur synchronisé avec les modifications du code source de l'application.
Fichier Docker Compose de développement
L'objectif principal d'un fichier Docker-compose local est de faciliter le lancement local de l'application pour corriger les bugs ou ajouter de nouvelles fonctionnalités.
Voici ce que nous souhaitons réaliser avec un fichier Docker-compose de développement local :
-
générer automatiquement l'image de développement à partir du code source de l'application locale et l'exécuter
-
S'assurer que les modifications du code source de l'application locale sont synchronisées dans le conteneur Docker
-
exposer les ports d'application pertinents à la machine locale afin d'accéder à l'application et d'effectuer des tests pendant le développement.
-
Exécuter automatiquement la base de données requise et injecter les données nécessaires à l'application.
services:
myapp-frontend:
# Build image from source code and Dockerfile
# present inside the current directory (.)
# Use 'dev' as the Dockerfile target to build the image
build:
context: .
target: dev
container_name: myapp-frontend
# Mount required config files inside the container
volumes:
- source: $PWD/.env
type: bind
read_only: true
target: /app/config/.env
# Set environment variables for the container
environment:
DEBUG: false
DATABASE_HOST: myapp-database
# Expose the container port 80 on local machine port 8080
ports:
- "8000:80"
# Make this service run only when the database service is ready
depends_on:
- myapp-database
# Keep the /app directory inside the container in sync
# with the local src directory containing the app source code
develop:
watch:
- action: sync
path: ./src
target: /app
myapp-database:
image: mysql:8.0.43-bookworm
container_name: myapp-database
environment:
MYSQL_ROOT_PASSWORD: "mysuperpass"
MYSQL_DATABASE: "myapp"
volumes:
# Initialize the database with a dump file
# when running for the first time
- source: /var/tmp/dump.sql
type: bind
read_only: false
target: /docker-entrypoint-initdb.d/dump.sql
# Make database data persist on the local machine disk
- source: /var/tmp/myapp-db-data
type: bind
read_only: false
target: /var/lib/mysql
Exécution de conteneurs avec Docker Compose
- Démarrer des conteneurs à l'aide de Docker Compose
# If the docker-compose.yaml configuration is
# located insite the current directory
# Start in the foreground
docker compose up
# When using the docker-compose.yaml
# develop.watch configuration directive
# you shoud use the --watch flag
docker compose up --watch
# Start in the background
docker compose up -d
# If the docker-compose.yaml configuration
# is located elsewhere, use the -f option to
# specify the path to the configuration file
docker compose -f $compose-config-file up
docker compose -f $compose-config-file up -d
- Arrêter et redémarrer des conteneurs à l'aide de Docker Compose
# Stop
docker compose [-f $compose-config-file] stop
# Restart
docker compose [-f $compose-config-file] restart
Vous souhaitez signaler une erreur, poser une question ou suggérer une amélioration ? N'hésitez pas à m'envoyer un e-mail à gmkziz@hackerstack.org.
Si vous aimez mes articles, pensez à vous inscrire à ma newsletter afin de recevoir les derniers articles dès qu'ils sont disponibles.
Prenez soin de vous, continuez à apprendre et à bientôt pour le prochain post 🚀