MotivĂ© par nos apprentissages prĂ©cĂ©dents, nous voilĂ enfin prĂšs â du moins, nous tenterons lâexercice â Ă bĂątir une image docker personnalisĂ©e.
Pré-requis:
Comment démarrer ce module?
Nous savons comment personnaliser un conteneur, puis le publier sur hub.docker.com.
Oui je sais, ce faisant, nous modifions le travail dâune autre personne.
Pas vraiment de mérite à celà .
Alors voici comment nous Ă©lever au dessus de la foule đšâđ©âđŠâđŠ:
Il est possible de crĂ©er un image personnalisĂ©e Ă partir dâun fichier de directives.
Quoique cela ne soit pas une obligation, ce fichier est habituellement nommĂ© âDockerfileâ.
Il propose la structure de directives suivante:
FROM â Ă partir dâune image existante, comme par exemple, âalpineâ
RUN â Dans lâimage, exĂ©cuter les commandes suivantes, comme par exemple, âapt update && apt install nanoâ
Note: RUN utilise la syntaxe â/bin/sh -câ pour exĂ©cuter la commande.
CMD â Lors du dĂ©marrage du conteneur, exĂ©cuter la commande suivante: â/bin/bashâ
RĂ©fĂ©rence: Best practices đĄ <â IMPORTANT, consulter!
Dans un dossier vide, créer un fichier Dockerfile.
# Ă partir d'une image
FROM ubuntu
# Optionnel
MAINTAINER Alain Boudreault <aboudrea@cstj.qc.ca>
LABEL Maintainer="Alain Boudreault <aboudrea@cstj.qc.ca>"
LABEL Auteur="Moi Moi"
# Exécuter des commandes dans l'image
# Dans la documentation de Dockerfile, il est recommandĂ© d'exĂ©cuter apt update et install sur la mĂȘme ligne!
RUN apt-get update && apt-get install nano -y
RUN mkdir /toto
# Commande à exécuter au démarrage d'un conteneur
CMD ["echo"," --------------------------\n","Ici la voix des Mistérons!\n","--------------------------\n"]
NOTE : Il est aussi possible dâutiliser
LABELau lieu deMAINTAINER, ce qui est prĂ©sentement la mĂ©thode recommandĂ©e.NOTE2 : Les directives peuvent-ĂȘtre inscrites en caractĂšres minuscules mais NON RECOMMANDĂ.
docker build$ docker build -t exercice01:latest .
NOTE : ATTENTION au . Ă la fin de la commande!!
Sortie de la construction (exemple) :
[+] Building 0.5s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 364B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/ubuntu:latest 0.4s
=> [1/2] FROM docker.io/library/ubuntu@sha256:703218c0465075f4425e58fac086e09e1de5c340b12976ab9eb8ad26615c3715 0.0s
=> CACHED [2/2] RUN apt-get update 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:6a083629e78cc15116597087eabb9cce0dea4180748039cd18a1d90487ec4a10 0.0s
=> => naming to docker.io/library/exercice01:latest
docker run $ docker run exercice01
--------------------------
Ici la voix des Mistérons!
--------------------------
Note : Si le fichier source nâest pas nommĂ© âDockerfileâ, il faut alors utiliser lâoption -f:
docker build -f Dockerfile02 -t docker_build_02 .
hello.sh#!/bin/bash
echo "------------------------------"
echo " Ici la voix des Mistérons!"
echo "------------------------------"
# Ă partir d'une image
FROM ubuntu
# Optionel
MAINTAINER Alain Boudreault <aboudrea@cstj.qc.ca>
# Exécuter des commandes dans l'image
RUN apt-get update
# Ajouter un fichier Ă l'image
ADD hello.sh /
RUN chmod a+x /hello.sh
# Commande à éxécuter au démarrage d'un conteneur
CMD ["/hello.sh"]
docker build -t exercice01:latest .
$ docker run exercice01

Modifier le projet pour obtenir le résultat suivant au démarrage du conteneur version 1.1:
docker run exercice01:1.1
Résultat :
--------------------------------------
Ici la voix des Mistérons!
--------------------------------------
Il est : 16:30:50 dans le conteneur
--------------------------------------
Note : Sous Linux, pour afficher lâheure dans un echo :
$(date +%X)
NOTE : DĂ©monstration de lâenseignant Ă partir dâun session Ubuntu â Explication des dĂ©pendances OS.
// Fichier: bonjour.cpp
// Exemple d'une application 'bin' pour Linux
// Par Alain Boudreault
// Fichier source compilé avec 'gcc' sous Ubuntu
// Il est possible d'avoir Ă installer g++ et gcc: apt install g++ gcc
#include <stdio.h>
#define MAXITERATION 10
/**
Incroyable mais oui, il n'y a qu'une seule fonction dans ce programme ;-)
*/
int main(){
for (int i=1; i <= MAXITERATION; i++) {
printf("%d\t - Bonjour 420-4D4 !!!\n", i);
} // for i
return 0;
} // main()
$ g++ bonjour.cpp -o bonjour
FROM ubuntu
COPY bonjour /bin
CMD ["/bin/bonjour"]
docker build -t alainboudreault/bonjour420:latest .
docker run alainboudreault/bonjour420

1 â Programmer un Dockerfile qui:
ubuntu:latestg++ (apt install -y -> installe sans confirmation)/bin2 â Produire et tester lâimage
3 â Publier lâimage sous docker-hub-login/bonjour-420:latest
4 â Copier le lien dans la conversation Teams
Suggestion, tester vos étapes dans un conteneur Ubuntu avant de rédiger le Dockerfile.
git, doit-ĂȘtre installĂ© sur votre poste de travail:
Ă partir de https://github.com/ve2cuy
Note: Placez-vous dans un dossier de travail vide!
git clone https://github.com/ve2cuy/superminou-depart
cd superminou-depart/
Ă partir du fichier âDockerfileâ ainsi que le contenu du dossier.
docker build -t test .
docker run -d -p 99:80 test
docker run -d -p 99:80 alainboudreault/420-4d4:exemple03


git, nano, mc et tmuxvotreCompteHub.docker.com/labo:4.0docker pull votreCompteHub.docker.com/labo:4.0
# EXEMPLE AVEC UN TIME-ZONE et autres choses ...
# Valeurs passées au conteneur par des variables d'environnement
# docker build -t test:v1 .
FROM ubuntu
LABEL maintainer="alain.boudreault@me.com"
RUN apt-get update && apt-get install -yq tzdata
# Ces variables d'environnement seront crées dans le conteneur.
ENV TZ="PoleNord/NoelVille"
ENV MOT_DE_PASSE_ULTRA_SECRET="j3sU1um0TDEp2SSE"
ENV ADRESSE_DNS=1.1.1.1
RUN apt install -y mc

Ă partir de lâexemple prĂ©cĂ©dent, contruire une image qui affiche le contenu des trois variables dâenvironnement au dĂ©marrage dâun conteneur.
Résultat obtenu:
$ docker run labo5.1.1.1
Bienvenue dans le conteneur Docker!
Le mot de passe ultra secret est: j3sU1um0TDEp2SSE
Le fuseau horaire est: PoleNord/NoelVille
Et le DNS est: 1.1.1.1
**NOTE: VĂ©rifier que la commande âmcâ fonctionne.

Voici comment passer des informations, pour le build, Ă partir de la ligne de commande:
# NOTE: BUILD_DATE et BUILD_VERSION seront passés via la ligne de commande.
FROM alpine
# Labels
ARG BUILD_DATE
ARG BUILD_VERSION
LABEL org.label-schema.schema-version="1.0"
LABEL org.label-schema.build-date=${BUILD_DATE}
LABEL org.label-schema.version=${BUILD_VERSION:-1.0}
LABEL org.label-schema.docker.cmd="docker run --build-arg BUILD_DATE="" --build-arg BUILD_VERSION=1.0 mes-outils"
LABEL maintainer="dev@unprojet.org"
ENV DATE_DU_BUILD=${BUILD_DATE}
# COPY outils.tar.gz /mnt
# WORKDIR /mnt
# RUN tar zxvf /mnt/outils.tar.gz
# CMD ["/mnt/outils/outils.sh"]
CMD ["sh", "-c", "echo -e BUILD_DATE=$DATE_DU_BUILD"]
Commande de construction :
# NOTE: --no-cache indique de reconstruire toutes les couches de l'image
docker build --no-cache=true \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg BUILD_VERSION=1.0 \
-f Dockerfile-labo-5.2 \
-t mes-outils:latest .
Résultat:
$ docker run mes-outils:latest
BUILD_DATE=2025-12-22T15:11:36Z
Inspecter la structure json du conteneur:
$ docker inspect ID_CONTENEUR | grep -A5 Labels
"Labels": {
"maintainer": "dev@unprojet.org",
"org.label-schema.build-date": "2025-12-22T15:21:48Z",
"org.label-schema.schema-version": "1.0",
"org.label-schema.version": "3.2"
},
# Autre méthode,
$ docker inspect --format='' ID_CONTENEUR
# 'index' pointe sur la clé "Config": {"Labels": {"org.label-schema.build-date": ... }
# Voir la structure json retounée par la directive 'inspect'.
$ docker inspect --format='' mes-outils
--> docker run --build-arg BUILD_DATE= --build-arg BUILD_VERSION=1.0 mes-outils
â ïž Note importante : Cet exemple utilise la syntaxe org.label-schema qui est maintenant dĂ©prĂ©ciĂ©e (mais toujours largement utilisĂ©e). Voir le document Dockerfile convention de nommage pour des exemples avec le standard âOCI Image Spec Annotationsâ.
La commande docker build --no-cache=true force Docker Ă reconstruire lâimage sans utiliser le cache des couches prĂ©cĂ©dentes.
Quand vous construisez une image Docker, chaque instruction du Dockerfile crĂ©e une couche (layer). Docker met en cache ces couches pour accĂ©lĂ©rer les builds futurs. Si rien nâa changĂ© dans une instruction, Docker rĂ©utilise la couche en cache au lieu de la reconstruire.
Exemple sans --no-cache :
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y python3
COPY app.py /app/
Lors du premier build : tout est construit.
Lors du second build : si seul app.py a changé, Docker réutilise le cache pour les commandes apt-get (qui sont lentes) et reconstruit seulement à partir de COPY.
--no-cache=true ?1. Obtenir les derniĂšres mises Ă jour
# Sans --no-cache : peut utiliser un ancien apt-get update
RUN apt-get update && apt-get install -y python3
# Avec --no-cache : récupÚre vraiment les derniers paquets
docker build --no-cache=true -t mon-image .
2. RĂ©soudre des problĂšmes de build Si votre build Ă©choue de maniĂšre inexplicable, le cache peut ĂȘtre corrompu ou obsolĂšte.
3. Télécharger de nouvelles versions
RUN pip install requests # Peut utiliser une version en cache
Avec --no-cache, pip télécharge la derniÚre version disponible.
4. Ăviter des comportements imprĂ©visibles Parfois Docker ne dĂ©tecte pas tous les changements (fichiers externes, variables dâenvironnement, etc.).
docker build --no-cache=true -t mon-image .
docker build --no-cache -t mon-image . # Ăquivalent
docker build --no-cache=false -t mon-image . # Utilise le cache (défaut)
Le build sera beaucoup plus long car Docker doit tout refaire depuis le début, y compris télécharger et installer les dépendances.
Conseil : Utilisez --no-cache uniquement quand câest nĂ©cessaire, pas systĂ©matiquement.
.dockerignoreSi prĂ©sent dans le rĂ©pertoire du Dockerfile, le fichier .dockerignore permet dâexclure des fichiers lorsque les directives âCOPYâ et âADDâ sont utilisĂ©es.
# Voici un exemple de contenu:
.git
.gitignore
node_modules
npm-debug.log
Dockerfile*
docker-compose*
README.md
LICENSE
.vscode
FROM nginx
# Se déplacer dans le dossier racine du serveur Web:
WORKDIR /usr/share/nginx/html
# Copier les fichiers du dossier courant vers le dossier WORKDIR
COPY . ./
COPY src dest: copie des fichiers/dossiers dâune source locale vers une destination dans lâimage.ADD src dest: Propose les fonctions de COPY et :
ADD rootfs.tar.gz .)Si les fonctions supplĂ©mentaires de ADD ne sont pas requises, il est recommandĂ© dâutiliser la directive COPY.
FROM scratch
ADD alpine-minirootfs-3.15.0-x86_64.tar.gz /
CMD ["/bin/sh"]
FROM alpine
RUN { \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=4000'; \
echo 'opcache.revalidate_freq=2'; \
echo 'opcache.fast_shutdown=1'; \
} > /config.ini
# Construire l'image et executer un shell interactif sur le conteneur
# pour vérifier le contenu du ficher config.ini

Contruire une image, Ă partir de âalpineâ qui crĂ©e le fichier dâalias suivant Ă partir dâun bloc âecho vers âŠâ Au besoin, revoir 5.7.
alias ll='ls -la'
alias t='top'
alias os='uname -a'
alias c='clear'
/etc/profile.d/alias.shNOTE: Pour fonctionner, il faut que la variable dâenvironnement ENV pointe sur /etc/profile
Résultat:
$ docker run -it --hostname labo5-7-1 labo-5-7-1
labo5-7-1:/# alias
c='clear'
os='uname -a'
t='top'
ll='ls -la'
labo5-7-1:/#
# docker build -f Dockerfile-labo-5-8-1 -t mon-httpd:8080 .
# docker run -d -p 8080:8080 --name web-server mon-httpd:8080
FROM httpd:2.4
# Exposer le port 8080 au lieu de 80
EXPOSE 8080
# Modifier la configuration pour écouter sur 8080
RUN sed -i 's/Listen 80/Listen 8080/' /usr/local/apache2/conf/httpd.conf
# Au besoin, copier votre contenu web
# COPY ./html/ /usr/local/apache2/htdocs/

Ă partir de lâexemple 5.8, contruire une image qui:
org.label-schema.docker.build.cmd avec la commande Ă exĂ©cuter pour construire lâimage.index.html dans le Dockerfile404.html dans le Dockerfile.
votre-compte/labo:5.8.1Copier la commande, pour lancer votre conteneur, dans la conversation TEAMS.
docker run -d -p 88:581 alainboudreault/labo:5.8.1
Les deux instructions servent Ă dĂ©finir ce qui sâexĂ©cute au dĂ©marrage dâun conteneur, mais elles fonctionnent diffĂ©remment.
CMD dĂ©finit la commande par dĂ©faut du conteneur. Elle peut ĂȘtre complĂštement remplacĂ©e lors du docker run.
Exemple :
FROM ubuntu
CMD ["echo", "Hello World"]
docker run mon-image
# Affiche : Hello World
docker run mon-image echo "Autre message"
# Affiche : Autre message (le CMD est remplacé)
ENTRYPOINT dĂ©finit le processus principal (PID 1) du conteneur. Ce processus contrĂŽle le cycle de vie du conteneur : quand il se termine, le conteneur sâarrĂȘte. Les arguments passĂ©s au docker run sont ajoutĂ©s Ă lâENTRYPOINT, sans le remplacer.
Exemple :
FROM ubuntu
ENTRYPOINT ["echo"]
docker run mon-image "Hello World"
# Affiche : Hello World
docker run mon-image "Bonjour"
# Affiche : Bonjour
Le processus défini par ENTRYPOINT devient le PID 1 du conteneur. Cela signifie :
docker stopExemple avec un serveur web :
FROM nginx
ENTRYPOINT ["nginx", "-g", "daemon off;"]
Le processus nginx devient le PID 1. Tant quâil tourne, le conteneur reste actif. Un docker stop envoie SIGTERM Ă nginx pour un arrĂȘt propre.
Câest lâusage le plus courant : ENTRYPOINT dĂ©finit lâexĂ©cutable principal (PID 1), CMD fournit des arguments par dĂ©faut.
Exemple :
FROM ubuntu
ENTRYPOINT ["curl"]
CMD ["--help"]
docker run mon-image
# Exécute : curl --help (processus principal)
docker run mon-image https://example.com
# Exécute : curl https://example.com (processus principal avec nouveaux args)
FROM php:7.4-apache
# persistent dependencies
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
# Ghostscript is required for rendering PDF previews
ghostscript \
; \
rm -rf /var/lib/apt/lists/*
# install the PHP extensions we need ([https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions](https://make.wordpress.org/hosting/handbook/handbook/server-environment/#php-extensions))
RUN set -ex; \
\
savedAptMark="$(apt-mark showmanual)"; \
\
apt-get update; \
apt-get install -y --no-install-recommends \
libfreetype6-dev \
libicu-dev \
libjpeg-dev \
libmagickwand-dev \
libpng-dev \
libwebp-dev \
libzip-dev \
; \
\
docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
--with-webp \
; \
docker-php-ext-install -j "$(nproc)" \
bcmath \
exif \
gd \
intl \
mysqli \
zip \
; \
# [https://pecl.php.net/package/imagick](https://pecl.php.net/package/imagick)
pecl install imagick-3.6.0; \
docker-php-ext-enable imagick; \
rm -r /tmp/pear; \
\
# some misbehaving extensions end up outputting to stdout
out="$(php -r 'exit(0);')"; \
[ -z "$out" ]; \
err="$(php -r 'exit(0);' 3>&1 1>&2 2>&3)"; \
[ -z "$err" ]; \
\
extDir="$(php -r 'echo ini_get("extension_dir");')"; \
[ -d "$extDir" ]; \
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
apt-mark auto '.*' > /dev/null; \
apt-mark manual $savedAptMark; \
ldd "$extDir"/*.so \
| awk '/=>/ { print $3 }' \
| sort -u \
| xargs -r dpkg-query -S \
| cut -d: -f1 \
| sort -u \
| xargs -rt apt-mark manual; \
\
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
rm -rf /var/lib/apt/lists/*; \
\
! { ldd "$extDir"/*.so | grep 'not found'; }; \
# check for output like "PHP Warning: PHP Startup: Unable to load dynamic library 'foo' (tried: ...)
err="$(php --version 3>&1 1>&2 2>&3)"; \
[ -z "$err" ]
# set recommended PHP.ini settings
# see [https://secure.php.net/manual/en/opcache.installation.php](https://secure.php.net/manual/en/opcache.installation.php)
RUN set -eux; \
docker-php-ext-enable opcache; \
{ \
echo 'opcache.memory_consumption=128'; \
echo 'opcache.interned_strings_buffer=8'; \
echo 'opcache.max_accelerated_files=4000'; \
echo 'opcache.revalidate_freq=2'; \
echo 'opcache.fast_shutdown=1'; \
} > /usr/local/etc/php/conf.d/opcache-recommended.ini
# [https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging](https://wordpress.org/support/article/editing-wp-config-php/#configure-error-logging)
RUN { \
# [https://www.php.net/manual/en/errorfunc.constants.php](https://www.php.net/manual/en/errorfunc.constants.php)
# [https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670](https://github.com/docker-library/wordpress/issues/420#issuecomment-517839670)
echo 'error_reporting = E_ERROR | E_WARNING | E_PARSE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_RECOVERABLE_ERROR'; \
echo 'display_errors = Off'; \
echo 'display_startup_errors = Off'; \
echo 'log_errors = On'; \
echo 'error_log = /dev/stderr'; \
echo 'log_errors_max_len = 1024'; \
echo 'ignore_repeated_errors = On'; \
echo 'ignore_repeated_source = Off'; \
echo 'html_errors = Off'; \
} > /usr/local/etc/php/conf.d/error-logging.ini
RUN set -eux; \
a2enmod rewrite expires; \
\
# [https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html](https://httpd.apache.org/docs/2.4/mod/mod_remoteip.html)
a2enmod remoteip; \
{ \
echo 'RemoteIPHeader X-Forwarded-For'; \
# these IP ranges are reserved for "private" use and should thus *usually* be safe inside Docker
echo 'RemoteIPTrustedProxy 10.0.0.0/8'; \
echo 'RemoteIPTrustedProxy 172.16.0.0/12'; \
echo 'RemoteIPTrustedProxy 192.168.0.0/16'; \
echo 'RemoteIPTrustedProxy 169.254.0.0/16'; \
echo 'RemoteIPTrustedProxy 127.0.0.0/8'; \
} > /etc/apache2/conf-available/remoteip.conf; \
a2enconf remoteip; \
# [https://github.com/docker-library/wordpress/issues/383#issuecomment-507886512](https://github.com/docker-library/wordpress/issues/383#issuecomment-507886512)
# (replace all instances of "%h" with "%a" in LogFormat)
find /etc/apache2 -type f -name '*.conf' -exec sed -ri 's/([[:space:]]*LogFormat[[:space:]]+"[^"]*)%h([^"]*")/\1%a\2/g' '{}' +
RUN set -eux; \
version='5.9.1'; \
sha1='15746f848cd388e270bae612dccd0c83fa613259'; \
\
curl -o wordpress.tar.gz -fL "[https://wordpress.org/wordpress-$version.tar.gz](https://wordpress.org/wordpress-$version.tar.gz)"; \
echo "$sha1 *wordpress.tar.gz" | sha1sum -c -; \
\
# upstream tarballs include ./wordpress/ so this gives us /usr/src/wordpress
tar -xzf wordpress.tar.gz -C /usr/src/; \
rm wordpress.tar.gz; \
\
# [https://wordpress.org/support/article/htaccess/](https://wordpress.org/support/article/htaccess/)
[ ! -e /usr/src/wordpress/.htaccess ]; \
{ \
echo '# BEGIN WordPress'; \
echo ''; \
echo 'RewriteEngine On'; \
echo 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; \
echo 'RewriteBase /'; \
echo 'RewriteRule ^index\.php$ - [L]'; \
echo 'RewriteCond %{REQUEST_FILENAME} !-f'; \
echo 'RewriteCond %{REQUEST_FILENAME} !-d'; \
echo 'RewriteRule . /index.php [L]'; \
echo ''; \
echo '# END WordPress'; \
} > /usr/src/wordpress/.htaccess; \
\
chown -R www-data:www-data /usr/src/wordpress; \
# pre-create wp-content (and single-level children) for folks who want to bind-mount themes, etc so permissions are pre-created properly instead of root:root
# wp-content/cache: [https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507](https://github.com/docker-library/wordpress/issues/534#issuecomment-705733507)
mkdir wp-content; \
for dir in /usr/src/wordpress/wp-content/*/ cache; do \
dir="$(basename "${dir%/}")"; \
mkdir "wp-content/$dir"; \
done; \
chown -R www-data:www-data wp-content; \
chmod -R 777 wp-content
VOLUME /var/www/html
COPY --chown=www-data:www-data wp-config-docker.php /usr/src/wordpress/
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["apache2-foreground"]
Document rédigé par Alain Boudreault © 2021-2026
Version 2025.12.22.1
Site par ve2cuy