Pour naviguer, utilisez les flĂšches en bas Ă droite (ou celles de votre clavier)
Gauche/Droite: changer de chapitre
Haut/Bas: naviguer dans un chapitre
Pour avoir une vue globale : utiliser la touche "o" (pour "Overview")
2026/2027

PrĂ©sentation disponible Ă lâadresse: https://gounthar.github.io/cours-devops-docker/main
Version PDF de la présentation : Cliquez ici
Contenu sous licence Creative Commons Attribution 4.0 International License
Code source de la présentation: https://github.com/gounthar/cours-devops-docker
Mis Ă jour le : 2 octobre 2026
Pour naviguer, utilisez les flĂšches en bas Ă droite (ou celles de votre clavier)
Gauche/Droite: changer de chapitre
Haut/Bas: naviguer dans un chapitre
Pour avoir une vue globale : utiliser la touche "o" (pour "Overview")

Bruno VERACHTEN
Community Manager & Platform Evangelist chez Vates, l’Ă©diteur de XCP-ng et Xen Orchestra đšđ»ââïž
Me contacter :
PremiĂšre itĂ©ration d’une dĂ©couverte Docker dans le cadre du DevOps
Contenu entiĂšrement libre et open-source
Méchamment basé sur le travail de Damien Duportal et Amaury Willemant
N’hĂ©sitez pas ouvrir des Pull Request si vous voyez des amĂ©liorations ou problĂšmes: sur cette page (đ wink wink)
đ§ Work in progress… đ§
22 septembre aprĂšs-midi
29 septembre aprĂšs-midi
06 octobre aprĂšs-midi
…
Pourquoi ? s’assurer que vous avez acquis un minimum de concepts
Quoi ? Sans doute une note sur 20, basée sur une liste de critÚres exhaustifs
Comment ? Un projet Gitlab Ă me rendre
Intro
Base
Containers
Images
Fichiers, nommage, inspect
Volumes
Réseaux
Docker Compose
Bonus
"La Base"

đ€ Quel est le problĂšme ?

đ€ Commençons plutĂŽt par une dĂ©finition:
Docker c’est …

đ€ DĂ©finition quelque peu datĂ©e (2014):
Docker is …

đ€ DĂ©finition quelque peu datĂ©e (2014):
Docker is a toolset for Linux containers designed to âbuild, ship and runâ distributed applications.

đ€ Nous voilĂ bien…
C’est quoi un container?

đ€ Nous voilĂ bien…
C’est quoi un container?
Depuis mars 2013, soit 13 ans dĂ©jĂ …

Mais moi aussi…

Aucune raison que ça soit réservé aux puissants! La révolution vaincra!

Linux everywhere… And then Docker to follow.






Histoire vraie.
|
Histoire vraie.
| |
cc |
Histoire vraie.
| |
cc | |
|
Histoire vraie.
| |
cc | |
| |
nan |
Histoire vraie.
| |
cc | |
| |
nan | |
|
Histoire vraie.
| |
cc | |
| |
nan | |
| |
pas standard dsl |
Histoire vraie.
| |
cc | |
| |
nan | |
| |
pas standard dsl |


ProblĂšme de temps exponentiel
L’IT n’est pas la seule industrie Ă rĂ©soudre des problĂšmes…

"Separation of Concerns"

"Virtualisation LégÚre"

Virtualisation

Virtualisation


BĂątie sur l’hyperviseur Xen : elle s’installe sur le matĂ©riel nu, donc bien du type 1
NĂ©e d’un fork de XenServer, aujourd’hui projet en incubation du Xen Project, hĂ©bergĂ© par la Linux Foundation
L’ISO se tĂ©lĂ©charge et s’installe sans licence : un type 1 que vous pouvez vraiment essayer, sur une machine que vous lui dĂ©diez â elle ne fera plus que ça
Sur un hĂŽte XCP-ng, ni bureau ni fenĂȘtre : la machine ne montre qu’une console
L’administration passe par un outil sĂ©parĂ©, qui parle Ă l’hĂŽte par le rĂ©seau
Xen Orchestra est celui de XCP-ng : visualiser, gérer, sauvegarder et déléguer un parc, depuis un navigateur
Sauvegardes complĂštes ou incrĂ©mentales, rĂ©plication, instantanĂ©s tournants â qui ne remplacent pas une sauvegarde
Deux façons de l’obtenir : l’appliance clĂ© en main, ou une installation depuis les sources
ââââââââââââââââââââââââ â vos conteneurs de TP â âââââââââââââââââââââââ†â la VM du Codespace â âââââââââââââââââââââââ†â un hyperviseur, â â chez l'hĂ©bergeur â âââââââââââââââââââââââ†â le matĂ©riel â ââââââââââââââââââââââââ
Depuis le premier TP, vos conteneurs tournent déjà dans une machine virtuelle : GitHub héberge chaque Codespace sur une VM
La question n’est donc jamais « VM ou conteneur ? », mais « qu’est-ce que je mets Ă quel Ă©tage ? »
Dans les TP, on ne travaillera qu’Ă l’Ă©tage du haut
"Virtualisation LégÚre"

LégÚre, vraiment?

Idée erronée mais citée trop fréquemment !










"Separation of concerns": 1 "tĂąche" par conteneur

Non exclusifs mutuellement


Docker sur Windows pour exécuter des conteneurs Linux :
Hyper-V : Sous Windows, Docker utilise souvent Hyper-V pour exécuter des machines virtuelles légÚres (VM) Linux.
Performance : Docker sur Windows pour les conteneurs Linux peut ĂȘtre lĂ©gĂšrement moins performant que Docker sur Linux natif.
Isolation : Isolation différente de celle des conteneurs Linux natifs.
Docker sur Linux pour exécuter des conteneurs Linux :
Native : Docker fonctionne nativement car il partage le mĂȘme noyau Linux.
Efficacité : Il est trÚs efficace en termes de consommation de ressources.
Isolation : Isolation basée sur les cgroups et les namespaces du noyau Linux.
Le bac Ă sable

Le bac Ă sable

Un environnement tout propre tout neuf !
Le bac Ă sable

Un environnement tout propre tout neuf !
Le poste de travail n’est pas impactĂ©
Le bac Ă sable

Un environnement tout propre tout neuf !
Le poste de travail n’est pas impactĂ©
La configuration reste également isolée sans effet sur le poste de travail
La machine de dév

La machine de dév

Avoir un environnement reproductible pour rendre homogÚne le développement et les tests.
La machine de dév

La machine de dév

Déploiement en production

Déploiement en production

Avec un container, si ca fonctionne en local, ca fonctionne en prod !
Outillage jetable

Outillage jetable

On instancie des applications sans les installer
Outillage jetable

On instancie des applications sans les installer
On crĂ©e le container, on l’utilise puis on le jette

On a la base, remplissons lĂ maintenant de containers!

Lorsque nous sommes sur le rĂ©seau de lâuniversitĂ©, Docker a du mal Ă tĂ©lĂ©charger directement les images depuis Docker Hub Ă cause des restrictions rĂ©seau. Nous sortons tous avec la mĂȘme adresse IP, et en peu de temps, elle sera blacklistĂ©e pour un moment, ce qui nous pĂ©nalisera tous. Pour rĂ©soudre ce problĂšme, lâuniversitĂ© met Ă disposition un serveur de cache local.
Avant toute configuration, introduisons les notions essentielles, que nous verrons en détail par la suite :
# Que sont les images Docker ?
docker image pull hello-world
docker container run hello-world
# Comment les images Docker sont nommées ?
[registry/][username/]image-name[:tag]
# Exemples :
library/ubuntu:22.04 # Image officielle (library est implicite)
ubuntu:22.04 # Identique Ă ci-dessus
user/mon-app:latest # Image personnalisĂ©eAu lieu de rĂ©cupĂ©rer les images directement depuis Docker Hub, nous utilisons le cache fourni par l’universitĂ©.
Au lieu de rĂ©cupĂ©rer les images directement depuis Docker Hub, nous utilisons le cache fourni par l’universitĂ©.
Avant (problĂšme) :
docker image pull ubuntu:22.04 # Ăchec sur le rĂ©seau universitaire, ou succĂšs temporaire avant d'ĂȘtre blacklistĂ©sAu lieu de rĂ©cupĂ©rer les images directement depuis Docker Hub, nous utilisons le cache fourni par l’universitĂ©.
Avant (problĂšme) :
docker image pull ubuntu:22.04 # Ăchec sur le rĂ©seau universitaire, ou succĂšs temporaire avant d'ĂȘtre blacklistĂ©sAprĂšs (solution) :
docker image pull cache-ili.univ-artois.fr:80/proxy_cache/library/ubuntu:22.04Option A : utiliser un registry mirror.
Option A : utiliser un registry mirror.
Modifier /etc/docker/daemon.json :
{
"registry-mirrors": ["http://cache-ili.univ-artois.fr:80/proxy_cache/"],
"insecure-registries": ["cache-ili.univ-artois.fr:80"]
}Option A : utiliser un registry mirror.
Modifier /etc/docker/daemon.json :
{
"registry-mirrors": ["http://cache-ili.univ-artois.fr:80/proxy_cache/"],
"insecure-registries": ["cache-ili.univ-artois.fr:80"]
}Désormais, les commandes Docker standards fonctionnent :
docker image pull ubuntu:22.04 # Utilise automatiquement le cache !
docker container run gradle # Fonctionne de maniĂšre transparenteDocker Hub nous voit tous avec la mĂȘme adresse IP, qui mĂšne au blacklist
Nous devons utiliser le serveur de cache local.
Docker Hub nous voit tous avec la mĂȘme adresse IP, qui mĂšne au blacklist
Nous devons utiliser le serveur de cache local.
sudo nano /etc/docker/daemon.jsonDocker Hub nous voit tous avec la mĂȘme adresse IP, qui mĂšne au blacklist
Nous devons utiliser le serveur de cache local.
sudo nano /etc/docker/daemon.jsonAjouter :
{
"registry-mirrors": ["http://cache-ili.univ-artois.fr:80/proxy_cache/"],
"insecure-registries": ["cache-ili.univ-artois.fr:80"]
}Docker Hub nous voit tous avec la mĂȘme adresse IP, qui mĂšne au blacklist
Nous devons utiliser le serveur de cache local.
sudo nano /etc/docker/daemon.jsonAjouter :
{
"registry-mirrors": ["http://cache-ili.univ-artois.fr:80/proxy_cache/"],
"insecure-registries": ["cache-ili.univ-artois.fr:80"]
}sudo systemctl restart dockersudo systemctl restart dockerdocker image pull ubuntu:22.04 # Utilise automatiquement le cache
docker container run python:3.9 # Fonctionne sans problĂšmedocker image pull ubuntu:22.04 # Utilise automatiquement le cache
docker container run python:3.9 # Fonctionne sans problĂšmeVotre client Docker â Cache universitaire â Docker Hub (si nĂ©cessaire) (cache-ili.univ-artois.fr)
Avantages : - Téléchargements plus rapides (images déjà en cache) - Fonctionne derriÚre le pare-feu universitaire - Pas besoin de changer vos commandes
Votre client Docker â Cache universitaire â Docker Hub (si nĂ©cessaire) (cache-ili.univ-artois.fr)
Avantages : - Téléchargements plus rapides (images déjà en cache) - Fonctionne derriÚre le pare-feu universitaire - Pas besoin de changer vos commandes
# Permission refusée ? Ajoutez votre utilisateur au groupe docker :
sudo usermod -aG docker $USER
newgrp docker # Ou déconnectez-vous / reconnectez-vous
# Toujours un souci ? Testez le cache :
docker image pull cache-ili.univ-artois.fr:80/proxy_cache/library/hello-worldLe cache de registre ne concerne que les images (docker image pull, FROM).
Ce qui se passe dans un conteneur ou dans un RUN (apk add, pip install, curl) sort sur le rĂ©seau et doit passer par le proxy HTTP de l’universitĂ© : cache-etu.univ-artois.fr:3128.
~/.docker/config.json (Recommandé)Créer ou modifier ~/.docker/config.json :
{
"proxies": {
"default": {
"httpProxy": "http://cache-etu.univ-artois.fr:3128",
"httpsProxy": "http://cache-etu.univ-artois.fr:3128",
"noProxy": "localhost,127.0.0.1"
}
}
}~/.docker/config.json (Recommandé)Le client Docker ajoute alors les variables de proxy :
Ă chaque nouveau conteneur (docker container run) ;
Ă chaque docker image build, comme arguments de build.
docker container run --rm alpine:3.24.2 env | grep -i proxy
# HTTP_PROXY=http://cache-etu.univ-artois.fr:3128
# HTTPS_PROXY=... NO_PROXY=... et les formes en minusculesL’image construite, elle, ne garde rien : docker image inspect ne montre que PATH dans Config.Env.
Aucun redĂ©marrage n’est nĂ©cessaire, et le Dockerfile reste le mĂȘme partout.
Les noms de conteneurs : noProxy ne les couvre pas. Un curl web2 entre deux conteneurs part vers le proxy.
Utilisez curl --noproxy '*' web2.
Maven ignore HTTP_PROXY et HTTPS_PROXY. Il lui faut un settings.xml (voir les travaux pratiques #7).
apk, wget, curl et git, eux, suivent les variables.
docker image build \
--build-arg HTTP_PROXY=http://cache-etu.univ-artois.fr:3128 \
--build-arg HTTPS_PROXY=http://cache-etu.univ-artois.fr:3128 .Dockerfile correspondant, sans ligne ARG :
FROM alpine:3.24.2
RUN apk add --no-cache curlHTTP_PROXY, HTTPS_PROXY, NO_PROXY (et leurs minuscules) sont des arguments prédéfinis : les RUN les voient sans déclaration.
ARGSi le Dockerfile dĂ©clare ARG HTTPS_PROXY, la valeur apparaĂźt dans l’historique de l’image :
docker image history --no-trunc --format '{{.CreatedBy}}' mon-image | grep -i proxy
# RUN |1 HTTPS_PROXY=http://cache-etu.univ-artois.fr:3128 /bin/sh -c apk add --no-cache curl # buildkit
# ARG HTTPS_PROXY=http://cache-etu.univ-artois.fr:3128Sans la ligne ARG, les arguments prédéfinis sont exclus de docker image history.
Ne déclarez donc pas les variables de proxy dans le Dockerfile.
Un proxy qui demande un login contient le mot de passe dans son URL.
Pour le passer Ă un seul RUN sans qu’il reste nulle part, BuildKit propose les secrets de build :
FROM alpine:3.24.2
RUN --mount=type=secret,id=proxy,env=HTTPS_PROXY \
apk add --no-cache curlread -rsp 'URL du proxy : ' PROXY_URL; echo # saisie masquée
PROXY_URL="$PROXY_URL" docker image build --secret id=proxy,env=PROXY_URL .
unset PROXY_URLLe mot de passe ne passe ni par l’historique du shell, ni par un export : seul ce docker image build le reçoit.
Le secret n’existe que pendant ce RUN : ni docker image history, ni Config.Env ne le contiennent.
ENV dans le Dockerfile : à éviterFROM alpine:3.24.2
ENV HTTPS_PROXY=http://cache-etu.univ-artois.fr:3128
RUN apk add --no-cache curlĂa fonctionne (ENV aprĂšs FROM et avant le RUN), mais la valeur est gravĂ©e dans l’image :
elle apparaĂźt dans docker image history et dans Config.Env ;
chaque conteneur lancĂ© depuis cette image, partout, essaiera de passer par le proxy de l’universitĂ© ;
si l’URL contient un mot de passe, il est livrĂ© avec l’image.
à réserver au dépannage, sur une image que vous ne partagez pas.
daemon.json : le proxy du démon seulement{
"proxies": {
"http-proxy": "http://cache-etu.univ-artois.fr:3128",
"https-proxy": "http://cache-etu.univ-artois.fr:3128",
"no-proxy": "localhost,127.0.0.1"
}
}Ce bloc de /etc/docker/daemon.json sert au dĂ©mon : pull et push vers les registres, communication entre nĆuds d’un swarm.
Il ne s’applique ni aux processus des conteneurs, ni aux RUN d’un build : apk add Ă©choue toujours.
Docker Desktop l’ignore (le proxy se rĂšgle dans ses paramĂštres).
| MĂ©thode | Conteneurs | RUN d’un build | Dans l’image |
|---|---|---|---|
| oui | oui | non |
| non | oui | non |
| non | oui | historique |
secret de build | non | un | non |
| oui | oui | oui |
| non | non | non |
Pour la salle de TP : ~/.docker/config.json.
Pour un proxy avec mot de passe : un secret de build.
Le cache de registre (registry-mirrors) reste utile, mais pour les images seulement.
Un navigateur web récent (et décent)
Un compte sur GitHub
GitHub Codespaces : Environnement de dĂ©veloppement dans le âïž "nuage"
But: reproductible et instantané
Puissance de calcul sur un serveur distant
Ăditeur VSCode dans le navigateur
Docker dĂ©jĂ installĂ© et configurĂ© đł
| Gratuit : 60h/mois pour compte GitHub standard đ |
| Gratuit : 120h/mois avec GitHub Student Developer Pack đ |
â Pas d’installation locale nĂ©cessaire
â Docker prĂ©-configurĂ© et prĂȘt Ă l’emploi
â Environnement identique pour tous les Ă©tudiants
â Accessible depuis n’importe quel ordinateur
â Sauvegarde automatique dans le cloud
â Gratuit et gĂ©nĂ©reux en ressources
Vous avez dĂ©jĂ ce qu’il faut ! â
Compte GitHub (créé précédemment)
đ Navigateur web moderne (Chrome, Firefox, Edge, Safari)
đ¶ Connexion Internet stable

Rendez-vous sur un dépÎt GitHub
Cliquez sur le bouton "Code" (en haut Ă droite) âŹïž
SĂ©lectionnez l’onglet "Codespaces"
Cliquez sur "Create codespace on main"

Centre : Ăditeur de code
â Coloration, autocomplĂ©tion
Bas : Terminal intégré
â Bash/Zsh, Docker
đĄ Identique Ă VSCode Desktop !
Le terminal est votre outil principal pour Docker !
# Vérifier l'utilisateur
whoami
# Résultat attendu : codespace
# Vérifier Docker
docker --version
# Résultat attendu : Docker version XX.XX.X
# Tester Docker
docker container run hello-world
Codespaces peut ĂȘtre personnalisĂ© avec .devcontainer/
// .devcontainer/devcontainer.json
{
"name": "Docker DevOps Course",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
}
}Vérifiez que tout fonctionne :
â Terminal ouvert (Ctrl+`)
â Commande whoami retourne codespace
â Commande docker --version affiche la version
â Commande docker container run hello-world s’exĂ©cute avec succĂšs
Optimisez votre quota gratuit de 60h/mois :
đ ArrĂȘter le Codespace quand vous ne l’utilisez pas
â»ïž RĂ©utiliser le mĂȘme Codespace (pas besoin d’en crĂ©er un nouveau)
đ Surveiller votre consommation : GitHub Settings > Billing
â±ïž ArrĂȘt automatique : Par dĂ©faut aprĂšs 30 min d’inactivitĂ©
Pour vous :
đ DĂ©marrage instantanĂ©
đŸ Pas de configuration locale
đ Environnement cohĂ©rent
đ± Accessible partout
Pour le cours :
â MĂȘme environnement pour tous
đ Debugging simplifiĂ©
đ Focus sur Docker, pas sur l’installation
đ Collaboration facile
Limites de Codespaces :
đ¶ NĂ©cessite une connexion Internet stable
â±ïž Quota mensuel (60h gratuit)
đ DĂ©pendance Ă GitHub
Alternative : Installation locale de Docker
â Pas de limite de temps
â Fonctionne hors-ligne
â Configuration plus complexe
Documentation officielle :
Support :
đ Questions pendant le cours
đŹ Forum/Discord du cours (si disponible)
đ GitHub Issues pour bugs
Ce que vous devez retenir :
âïž Environnement de dĂ©veloppement cloud
đł Docker prĂ©-installĂ© et prĂȘt
đ 60h/mois gratuit (largement suffisant)
đ DĂ©marrage en 30 secondes
đ» Interface VSCode familiĂšre
đ Penser Ă arrĂȘter pour Ă©conomiser
Le piĂšge : apt install docker.io n’installe pas ce qu’il faut
docker compose, ni docker buildx, et Docker demande de retirer ce paquetdocker.io ? đ€Ce que donne apt install docker.io
docker compose version
# docker: unknown command: docker compose
docker buildx version
# docker: unknown command: docker buildxSuggests:, donc apt ne les installe pasEt la doc Docker demande de le retirer
docker.io figure sur la liste officielle des paquets Ă dĂ©sinstaller avant d’installer Docker,
aux cĂŽtĂ©s de podman-docker.Sur le rĂ©seau de l’universitĂ©, apt et curl doivent passer par le proxy.
Définir http_proxy dans son shell ne suffit pas : sudo efface les
variables d’environnement.
sudo tee /etc/apt/apt.conf.d/99proxy <<'PROXY'
Acquire::http::Proxy "http://cache-etu.univ-artois.fr:3128/";
Acquire::https::Proxy "http://cache-etu.univ-artois.fr:3128/";
PROXY
export http_proxy=http://cache-etu.univ-artois.fr:3128/
export https_proxy=http://cache-etu.univ-artois.fr:3128/sudo apt remove $(dpkg --get-selections \
docker.io docker-compose docker-compose-v2 \
docker-doc docker-buildx podman-docker \
containerd runc | cut -f1)sudo apt update
sudo apt install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /tmp/docker.asc
sudo install -m 0644 /tmp/docker.asc /etc/apt/keyrings/docker.asccurl sans sudo, volontairement : lancé en root il perdrait votre proxysudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt updatesudo apt install docker-ce docker-ce-cli containerd.io \
docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker $USERdocker s’appliquedocker --version
docker compose version
docker buildx version
docker container run hello-worlddocker compose et docker buildx répondentSymptÎme : docker semble installé, mais répond
Cannot connect to the Docker daemon
podman-docker qui pose un /usr/bin/docker redirigeant vers Podman. Podman seul
ne fournit aucune commande docker : on obtiendrait command not found.# Qui répond, au juste ?
command -v docker
echo $DOCKER_HOST
docker context ls
# Retirer le wrapper suffit
sudo apt remove podman-dockerSymptĂŽme : curl ou apt restent muets, puis expirent. Pourtant
echo $http_proxy affiche bien le proxy, et la mĂȘme commande sans sudo passe.
sudo repart d’un environnement neuf : sudoers(5) appelle ça env_reset, actif
par dĂ©faut. http_proxy n’est dans aucune liste conservĂ©e, donc le curl lancĂ© en
root sort en direct et se fait jeter par le réseau.# Le proxy est-il visible par root ?
sudo printenv http_proxy
# (aucune sortie)
# Trois sorties possibles
sudo -E curl ...
curl ... puis sudo install ...
/etc/apt/apt.conf.d/99proxysudo : c’est ce qui rend la panne longue
Ă diagnostiquerVotre environnement Docker est maintenant configurĂ© đ
â GitHub Codespaces : Docker dĂ©jĂ installĂ©
â Ubuntu 26.04 : Docker installĂ© par le dĂ©pĂŽt officiel
â PrĂȘt pour la suite !
Avant de dĂ©couvrir Docker, un dĂ©tour essentiel…
La ligne de commande (CLI)
đ„ïž Interface fondamentale pour Docker
đ§ Base de tous les outils DevOps
đȘ CompĂ©tence indispensable pour automatiser
Votre Codespace tourne. Docker répond. Tout marche.
Et sur la machine du voisin ?
Un fichier dans .github/workflows/, et GitHub exécute vos commandes à chaque push.
name: Bonjour
on:
- push
jobs:
dire_bonjour:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7 # RĂ©cupĂšre le contenu du dĂ©pĂŽt correspondant au commit du workflow en coursruns-on : la machine que GitHub vous prĂȘte. steps : ce qu’elle fait.Votre Codespace
devcontainers/base:ubuntu
Vos outils, votre version
Le runner par défaut
ubuntu-latest
Les outils de GitHub, leurs versions
container:
image: mcr.microsoft.com/devcontainers/base:ubuntu # La mĂȘme image que le Codespacesudo : inutile, dans un conteneur le job tourne dĂ©jĂ en root (uid=0)cowsay â installĂ© dans /usr/games, absent du PATH d’un shell non interactifREADME.md â ce dĂ©pĂŽt n’en a pas. Il a un README.adocBut : faire dire le README du dĂ©pĂŽt par une vache, depuis la CI
đ·đœââïž Ă vous d’Ă©crire le workflow :
đĄ le job doit tourner dans la mĂȘme image que votre Codespace
đĄ cowsay n’est pas installĂ© dans l’image : il faut l’ajouter
đĄ pensez Ă rĂ©cupĂ©rer le contenu du dĂ©pĂŽt avant de le lire
name: Bonjour
on:
- push
jobs:
dire_bonjour:
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/devcontainers/base:ubuntu # La mĂȘme image que le Codespace
steps:
- uses: actions/checkout@v7 # RécupÚre le contenu du dépÎt correspondant au commit du workflow en cours
- run: | # Pas de `sudo` : dans un conteneur, le job tourne déjà en root
apt-get update
apt-get install -y cowsay
- run: cat README.adoc | /usr/games/cowsay # `cowsay` s'installe dans /usr/games, absent du PATHLes diapos que vous regardez sortent de .github/workflows/build-workflow.yml
docker compose version â make build â make verify â publicationpush. Y compris celui qui a corrigĂ© la faute de frappe que vous venez de lire..github/workflows/container: fait tourner le job dans votre image, pas celle de GitHubGuide de survie
Communication Humain <→ Machine
Base commune de TOUS les outils
đŹđ§ CLI == "Command Line Interface"
đ«đ· "Interface de Ligne de Commande"
Pour les théoriciens et curieux :
đŹđ§ REPL == "Readâevalâprint loop"
ls --color=always -l /binSĂ©parateur : l’espace
Premier Ă©lĂ©ment (ls) : c’est la commande
Les éléments commençant par un tiret - sont des "options" et/ou drapeaux ("flags")
"Option" == "Optionnel"
Les autres éléments sont des arguments (/bin)
Nécessaire (par opposition)
Afficher le manuel de <commande> :
# Commande 'man' avec comme argument le nom de ladite commande
man <commande>Navigation avec les flĂšches haut et bas
Tapez / puis une chaĂźne de texte pour chercher
Touche n pour sauter dâoccurrence en occurrence
Touche q pour quitter
đ Essayez avec ls, chercher le mot color
đĄ La majoritĂ© des commandes fournit Ă©galement une option (--help), un flag (-h) ou un argument (help)
Google c’est pratique aussi hein !
Dans un terminal Unix/Linux/WSL :
CTRL + C : Annuler le process ou prompt en cours
CTRL + L : Nettoyer le terminal
CTRL + A : Positionner le curseur au début de la ligne
CTRL + E : Positionner le curseur Ă la fin de la ligne
CTRL + R : Rechercher dans l’historique de commandes
pwd : Afficher le répertoire courant
đ Option -P ?
ls : Lister le contenu du répertoire courant
đ Options -a et -l ?
cd : Changer de répertoire
đ Sans argument : que se passe t’il ?
cat : Afficher le contenu d’un fichier
đ Essayez avec plusieurs arguments
mkdir : créer un répertoire
đ Option -p ?
echo : Afficher un (des) message(s)
rm : Supprimer un fichier ou dossier
touch : Créer un fichier
grep : Chercher un motif de texte
Le systĂšme de fichier a une structure d’arbre
La racine du disque dur c’est / : đ ls -l /
Le sĂ©parateur c’est Ă©galement / : đ ls -l /usr/bin
Deux types de chemins :
Absolu (depuis la racine): Commence par / (Ex. /usr/bin)
Sinon c’est relatif (e.g. depuis le dossier courant) (Ex ./bin ou local/bin/)

Le dossier "courant" c’est . : đ ls -l ./bin # Dans le dossier /usr
Le dossier "parent" c’est .. : đ ls -l ../ # Dans le dossier /usr
~ (tilde) c’est un raccourci vers le dossier de l’utilisateur courant : đ ls -l ~
Sensible Ă la casse (majuscules/minuscules) et aux espaces : đ
ls -l /bin
ls -l /Bin
mkdir ~/"Accent tué"
ls -d ~/Accent\ tuéVariables interpolées avec le caractÚre "dollar" $ :
echo $MA_VARIABLE
echo "$MA_VARIABLE"
echo ${MA_VARIABLE}
# Recommendation
echo "${MA_VARIABLE}"
MA_VARIABLE="Salut tout le monde"
echo "${MA_VARIABLE}"Sous commandes avec $(<command>):
echo ">> Contenu de /tmp :\n$(ls /tmp)"Des if, des for et plein d’autres trucs (https://tldp.org/LDP/abs/html/)
Chaque exĂ©cution de commande renvoie un code de retour (đŹđ§ "exit code")
Nombre entier entre 0 et 255 (en POSIX)
Code accessible dans la variable éphémÚre $? :
ls /tmp
echo $?
ls /do_not_exist
echo $?
# Une seconde fois. Que se passe-t'il ?
echo $?
ls -l /tmp
echo "Hello" > /tmp/hello.txt
ls -l /tmp
ls -l /tmp >/dev/null
ls -l /tmp 1>/dev/null
ls -l /do_not_exist
ls -l /do_not_exist 1>/dev/null
ls -l /do_not_exist 2>/dev/null
ls -l /tmp /do_not_exist
ls -l /tmp /do_not_exist 1>/dev/null 2>&1Le caractĂšre "pipe" | permet de chaĂźner des commandes
Le "stdout" de la premiÚre commande est branchée sur le "stdin" de la seconde
Exemple : Afficher les fichiers/dossiers contenant le lettre d dans le dossier /bin :
ls -l /bin
ls -l /bin | grep "d" --color=autoLes commandes sont des fichier binaires exécutables sur le systÚme :
command -v cat # équivalent de "which cat"
ls -l "$(command -v cat)"La variable d’environnement $PATH liste les dossiers dans lesquels chercher les binaires
đĄ Utiliser cette variable quand une commande fraĂźchement installĂ©e n’est pas trouvĂ©e
Exécution de scripts :
Soit appel direct avec l’interprĂ©tateur : sh ~/monscript.txt
Soit droit d’exĂ©cution avec un "shebang" (e.g. #!/bin/bash)
$ chmod +x ./monscript.sh
$ head -n1 ./monscript.sh
#!/bin/bash
$ ./monscript.sh
# ExécutionGuide de survie
Support de communication
Humain → Machine
Humain <→ Humain
Besoins de traçabilité, de définition explicite et de gestion de conflits
Collaboration requise pour chaque changement (revue, responsabilités)
avec un VCS : đŹđ§ Version Control System
Pour conserver une trace de tous les changements dans un historique
"Source unique de vĂ©ritĂ©" (đŹđ§ Single Source of Truth)
Pour collaborer efficacement sur un mĂȘme rĂ©fĂ©rentiel

Nous allons utiliser Git
Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency.

L’historique ("Version Database") : dossier .git
Dossier de votre projet ("Working Directory") - Commande
La zone d’index ("Staging Area")

Dans le terminal de votre Codespace :
Créez un dossier vide nommé projet-vcs-1 dans le répertoire /workspaces, puis positionnez-vous dans ce dossier
mkdir -p /workspaces/projet-vcs-1/
cd /workspaces/projet-vcs-1/Est-ce qu’il y a un dossier .git/ ?
Essayez la commande git status ?
Initialisez le dépÎt git avec git init
Est-ce qu’il y a un dossier .git/ ?
Essayez la commande git status ?
mkdir -p /workspaces/projet-vcs-1/
cd /workspaces/projet-vcs-1/
ls -la # Pas de dossier .git
git status # Erreur "fatal: not a git repository"
git init ./
ls -la # On a un dossier .git
git status # SuccÚs avec un message "On branch main No commits yet"Créez un fichier README.md dedans avec un titre et vos nom et prénoms
Essayez la commande git status ?
Ajoutez le fichier Ă la zone d’indexation Ă l’aide de la commande git add (…)
Essayez la commande git status ?
Créez un commit qui ajoute le fichier README.md avec un message,
Ă l’aide de la commande git commit -m <message>
Essayez la commande git status ?
echo "# Read Me\n\nObi Wan" > ./README.md
git status # Message "Untracked file"
git add ./README.md
git status # Message "Changes to be committed"
git commit -m "Ajout du README au projet"
git status # Message "nothing to commit, working tree clean"diff: un ensemble de lignes "changées" sur un fichier donné

changeset: un ensemble de "diff" (donc peut couvrir plusieurs fichiers)

commit: un changeset qui possÚde un (commit) parent, associé à un message

"HEAD": C’est le dernier commit dans l’historique


Afficher la liste des commits
Afficher le changeset associé à un commit
Modifier du contenu dans README.md et afficher le diff
Annulez ce changement sur README.md
git log
git show # Show the "HEAD" commit
echo "# Read Me\n\nObi Wan Kenobi" > ./README.md
git diff
git status
git checkout -- README.md
git statusAbstraction d’une version "isolĂ©e" du code
ConcrĂštement, une branche est un alias pointant vers un "commit"

Créer une branche nommée feature/html
Ajouter un nouveau commit contenant un nouveau fichier index.html sur cette branche
Afficher le graphe correspondant Ă cette branche avec git log --graph
git switch --create feature/html
# Ou alors: git branch feature/html && git switch feature/html
echo '<h1>Hello</h1>' > ./index.html
git add ./index.html && git commit --message="Ajout d'une page HTML par défaut" # -m / --message
git log
git log --graph
# 'git lg' n'existe pas par défaut : c'est un alias, à définir une fois pour toutes
git config --global alias.lg "log --graph --oneline --decorate --all"
git lg # cat ~/.gitconfig => l'alias apparaĂźt maintenant dans la section [alias]On intĂšgre une branche dans une autre en effectuant un merge
Un nouveau commit est créé, fruit de la combinaison de 2 autres commits

Merger la branche feature/html dans la branche principale
â ïž Pensez Ă utiliser l’option --no-ff
Afficher le graphe correspondant Ă cette branche avec git log --graph
git switch main
git merge --no-ff feature/html # Enregistrer puis fermer le fichier 'MERGE_MSG' qui a été ouvert
git log --graph
# git lg"Infrastructure as Code" :
Besoins de traçabilité, de définition explicite et de gestion de conflits
Collaboration requise pour chaque changement (revue, responsabilités)
Code Civil:
git est un des (plus populaires) de systĂšme de contrĂŽle de versions
Cet outil vous permet:
D’avoir un historique auditable de votre code source
De collaborer efficacement sur le code source (conflit git == "PARLEZ-VOUS")
⇒ C’est une ligne de commande (trop?) complĂšte qui nĂ©cessite de pratiquer
C’est Ă vous (ouf) !
Allez dans votre Codespace
Dans un terminal, tapez la commande suivante :
docker container run hello-world
# Equivalent de l'ancienne commande 'docker run'Depuis Docker 1.13 (2017), les commandes qui agissent sur un objet (conteneur,
image, rĂ©seau, volume) s’Ă©crivent aussi docker OBJET VERBE, qui dit sur quel
type d’objet on agit.
| Raccourci historique | Forme de gestion |
|---|---|
|
|
|
|
|
|
|
|
|
|
Les deux formes marchent, et les raccourcis ne sont pas dépréciés
docker container cp --help liste d’ailleurs les deux sous Aliases:
â ïž Le raccourci cache parfois l’objet : docker pull tĂ©lĂ©charge une image,
donc la forme longue est docker image pull, pas docker container pull
đ« Les commandes qui n’agissent sur aucun objet gardent leur forme unique :
docker login, docker version, docker search
đ Dans ce cours, on Ă©crit toujours la forme de gestion. Vous rencontrerez les raccourcis partout ailleurs : sachez les lire.
Un service "Docker Engine" tourne en tĂąche de fond et publie une API REST
La commande docker container run … a lancĂ© un client docker qui a envoyĂ© une requĂȘte POST au service docker
Le service a téléchargé une Image Docker depuis le registre DockerHub,
Puis a exécuté un conteneur basé sur cette image


C’est Ă vous !
docker container ls --help
# ...
docker container ls
# ...
docker container ls --all⇒ đ€ comment comprenez vous les rĂ©sultats des 2 derniĂšres commandes ?
Le conteneur est toujours prĂ©sent dans le "Docker Engine" mĂȘme en Ă©tant arrĂȘtĂ©
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
109a9cdd3ec8 hello-world "/hello" 33 seconds ago Exited (0) 17 seconds ago festive_faradayUn conteneur == une commande "conteneurisée"
cf. colonne "COMMAND"
Quand la commande s’arrĂȘte : le conteneur s’arrĂȘte
cf. code de sortie dans la colonne "STATUS"
$ docker container run busybox echo hello world$ docker container run busybox echo hello worldLe moteur Docker crĂ©e un container Ă partir de l’image "busybox".
$ docker container run busybox echo hello worldLe moteur Docker crĂ©e un container Ă partir de l’image "busybox".
Le moteur Docker démarre le container créé.
$ docker container run busybox echo hello worldLe moteur Docker crĂ©e un container Ă partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exĂ©cutĂ©e Ă l’intĂ©rieur du container.
$ docker container run busybox echo hello worldLe moteur Docker crĂ©e un container Ă partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exĂ©cutĂ©e Ă l’intĂ©rieur du container.
On obtient le résultat dans la sortie standard de la machine hÎte.
$ docker container run busybox echo hello worldLe moteur Docker crĂ©e un container Ă partir de l’image "busybox".
Le moteur Docker démarre le container créé.
La commande "echo hello world" est exĂ©cutĂ©e Ă l’intĂ©rieur du container.
On obtient le résultat dans la sortie standard de la machine hÎte.
Le container est stoppé.
Outillage jetable

Tester une version de Maven, de JDK, de NPM, âŠ
Lancez un nouveau conteneur nommé bonjour
đĄ docker container run --help ou Documentation en ligne
Affichez les "logs" du conteneur (==traces d’exĂ©cution Ă©crites sur le stdout + stderr de la commande conteneurisĂ©e)
đĄ docker container logs --help ou Documentation en ligne
Lancez le conteneur avec la commande docker container start
Regardez le résultat dans les logs
Supprimez le container avec la commande docker container rm
docker container run --name=bonjour hello-world
# Affiche le texte habituel
docker container logs bonjour
# Affiche le mĂȘme texte : pratique si on a fermĂ© le terminal
docker container start bonjour
# N'affiche pas le texte mais l'identifiant unique du conteneur 'bonjour'
docker container logs bonjour
# Le texte est affiché 2 fois !
docker container ls --all
# Le conteneur est présent
docker container rm bonjour
docker container ls --all
# Le conteneur n'est plus là : il a été supprimé ainsi que ses logs
docker container logs bonjour
# Error: No such container: bonjourC’est une "image" de conteneur, c’est Ă dire un modĂšle (template) reprĂ©sentant une application auto-suffisante.
On peut voir ça comme un "paquetage" autonome
C’est un systĂšme de fichier complet:
Il y a au moins une racine /
Ne contient que ce qui est censĂ© ĂȘtre nĂ©cessaire (dĂ©pendances, librairies, binaires, etc.)
https://hub.docker.com/ : C’est le registre d’images "par dĂ©faut"
Exemple : Image officielle de conteneur "Ubuntu"
đ Cherchez l’image hello-world pour en voir la page de documentation
đĄ pas besoin de crĂ©er de compte pour ça
Il existe d’autre "registres" en fonction des besoins (GitHub GHCR, Google GCR, etc.)
docker container run --interactive --tty alpinedocker container run --interactive --tty alpine
/ $docker container run --interactive --tty alpine
/ $On lance un container Ă partir de l’image "alpine"
docker container run --interactive --tty alpine
/ $On lance un container Ă partir de l’image "alpine"
On lance un sh dans ce container
docker container run --interactive --tty alpine
/ $On lance un container Ă partir de l’image "alpine"
On lance un sh dans ce container
On redirige l’entrĂ©e standard avec -i
docker container run --interactive --tty alpine
/ $On lance un container Ă partir de l’image "alpine"
On lance un sh dans ce container
On redirige l’entrĂ©e standard avec -i
On déclare un pseudo-terminal avec -t
Quelle distribution Linux est utilisée dans le terminal Codespaces ?
đĄ Regardez le fichier /etc/os-release
ExĂ©cutez un conteneur interactif basĂ© sur alpine:3.24.2 (une distribution Linux ultra-lĂ©gĂšre) et regardez le contenu du fichier au mĂȘme emplacement
đĄ docker container run --help
đĄ Demandez un tty Ă Docker
đĄ Activez le mode interactif
ExĂ©cutez la mĂȘme commande dans un conteneur basĂ© sur la mĂȘme image mais en NON interactif
đĄ Comment surcharger la commande par dĂ©faut ?
$ cat /etc/os-release
# ... Ubuntu ....
$ docker container run --tty --interactive alpine:3.24.2
/ $ cat /etc/os-release
# ... Alpine ...
# Notez que le "prompt" du terminal est différent DANS le conteneur
/ $ exit
$ docker container ls --all
$ docker container run alpine:3.24.2 cat /etc/os-release
# ... Alpine ...Revenons dans notre container interactif de tout Ă l’heure…
/ $ curl google.fr/ $ curl google.fr
/bin/sh: curl: not found/ $ curl google.fr
/bin/sh: curl: not foundcURL n’est pas disponible par dĂ©faut sur Alpine. Il faut l’installer au prĂ©alable
/ $ curl google.fr
/bin/sh: curl: not foundcURL n’est pas disponible par dĂ©faut sur Alpine. Il faut l’installer au prĂ©alable
/ $ apk update && apk add curl/ $ curl google.fr
/bin/sh: curl: not foundcURL n’est pas disponible par dĂ©faut sur Alpine. Il faut l’installer au prĂ©alable
/ $ apk update && apk add curl
v3.24.2-58-g29b9ec24b1b [https://dl-cdn.alpinelinux.org/alpine/v3.24/main]
v3.24.2-61-g887b5770c3e [https://dl-cdn.alpinelinux.org/alpine/v3.24/community]
OK: 28651 distinct packages available
(1/9) Installing brotli-libs (1.2.0-r1)
(2/9) Installing c-ares (1.34.8-r0)
(3/9) Installing libunistring (1.4.2-r0)
(4/9) Installing libidn2 (2.3.8-r0)
(5/9) Installing nghttp2-libs (1.69.0-r0)
(6/9) Installing libpsl (0.21.5-r3)
(7/9) Installing zstd-libs (1.5.7-r2)
(8/9) Installing libcurl (8.22.0-r0)
(9/9) Installing curl (8.22.0-r0)
Executing busybox-1.37.0-r31.trigger
OK: 13.1 MiB in 25 packages/ $ curl google.fr
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="http://www.google.fr/">here</A>.
</BODY></HTML>C’est bon, on a cURL đб
On peut quitter sh et revenir Ă la machine hĂŽte !
/ $ exitOn peut quitter sh et revenir Ă la machine hĂŽte !
/ $ exitSi on veut rĂ©utiliser cURL sur Alpine, c’est simple, on relance le shell, non? đ€
On peut quitter sh et revenir Ă la machine hĂŽte !
/ $ exitSi on veut rĂ©utiliser cURL sur Alpine, c’est simple, on relance le shell, non? đ€
docker container run --interactive --tty alpineOn peut quitter sh et revenir Ă la machine hĂŽte !
/ $ exitSi on veut rĂ©utiliser cURL sur Alpine, c’est simple, on relance le shell, non? đ€
docker container run --interactive --tty alpineOn relance cURL:
/ $ curl google.frOn peut quitter sh et revenir Ă la machine hĂŽte !
/ $ exitSi on veut rĂ©utiliser cURL sur Alpine, c’est simple, on relance le shell, non? đ€
docker container run --interactive --tty alpineOn relance cURL:
/ $ curl google.fr
/bin/sh: curl: not foundEn fait, c’est logique !
docker container runCette commande instancie un "nouveau container Ă chaque fois" !
En fait, c’est logique !
docker container runCette commande instancie un "nouveau container Ă chaque fois" !
Chaque container est différent.
En fait, c’est logique !
docker container runCette commande instancie un "nouveau container Ă chaque fois" !
Chaque container est différent.
Aucun partage entre les containers Ă part le contenu de base de l’image.

" On m’a vendu un truc qui permet de lancer des tonnes de microservices⊠mais lĂ , on tĂ©lĂ©charge nimps et s’amuse Ă le perdreâŠ"
Lançons un container bien particulierâŠ
docker container run --interactive --tty jpetazzo/clockLançons un container bien particulierâŠ
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...Lançons un container bien particulierâŠ
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...Ce container va tourner indĂ©finiment sauf si on le stoppe avec âšïž Ctrl+CâŠ
Lançons un container bien particulierâŠ
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...Ce container va tourner indĂ©finiment sauf si on le stoppe avec âšïž Ctrl+CâŠ
⊠mais ca va stopper le container !

Lançons un container bien particulierâŠ
docker container run --interactive --tty jpetazzo/clock
Mon Sep 25 10:33:51 UTC 2023
Mon Sep 25 10:33:52 UTC 2023
Mon Sep 25 10:33:53 UTC 2023
Mon Sep 25 10:33:54 UTC 2023
Mon Sep 25 10:33:55 UTC 2023
Mon Sep 25 10:33:56 UTC 2023
...Ce container va tourner indĂ©finiment sauf si on le stoppe avec âšïž Ctrl+CâŠ
⊠mais ca va stopper le container !
Oui car quand on stoppe le processus principal d’un container, ce dernier n’a plus de raison d’exister et s’arrĂȘte naturellement.
La solution : le flag --detach
docker container run --detach jpetazzo/clockLa solution : le flag --detach
docker container run --detach jpetazzo/clock
399f3b23bc0585991afa80dfee854cf0a953d782b99153b4e2cbc74ab6b07770Le retour de cette commande correspond Ă l’identifiant unique du container.
Cette fois-ci, le container tourne, mais en arriĂšre plan !

" Okay, maintenant il n’Ă©crit la date nulle part⊠mais toutes les secondes⊠à l’aide ! "
Le processus principal de ce container écrit dans la sortie standard⊠du container !
Comment retrouver le contenu de la sortie standard du container ?
Le processus principal de ce container écrit dans la sortie standard⊠du container !
Comment retrouver le contenu de la sortie standard du container ?
docker container logs 399f3Le processus principal de ce container écrit dans la sortie standard⊠du container !
Comment retrouver le contenu de la sortie standard du container ?
docker container logs 399f3
Tue Sep 12 12:37:01 UTC 2023
Tue Sep 12 12:37:02 UTC 2023
Tue Sep 12 12:37:03 UTC 2023
Tue Sep 12 12:37:04 UTC 2023
Tue Sep 12 12:37:05 UTC 2023
Tue Sep 12 12:37:06 UTC 2023
Tue Sep 12 12:37:07 UTC 2023
Tue Sep 12 12:37:08 UTC 2023
Tue Sep 12 12:37:09 UTC 2023
Tue Sep 12 12:37:10 UTC 2023
Tue Sep 12 12:37:11 UTC 2023Le processus principal de ce container écrit dans la sortie standard⊠du container !
Comment retrouver le contenu de la sortie standard du container ?
docker container logs 399f3
Tue Sep 12 12:37:01 UTC 2023
Tue Sep 12 12:37:02 UTC 2023
Tue Sep 12 12:37:03 UTC 2023
Tue Sep 12 12:37:04 UTC 2023
Tue Sep 12 12:37:05 UTC 2023
Tue Sep 12 12:37:06 UTC 2023
Tue Sep 12 12:37:07 UTC 2023
Tue Sep 12 12:37:08 UTC 2023
Tue Sep 12 12:37:09 UTC 2023
Tue Sep 12 12:37:10 UTC 2023
Tue Sep 12 12:37:11 UTC 2023Ouf ! On n’est pas obligĂ© de saisir l’identifiant complet ! Il suffit de fournir le nombre suffisant de caractĂšres pour que ce soit discriminant.
Comment savoir si j’ai des containers en cours d’exĂ©cution ?
đĄ Commande vue un peu plus tĂŽt…
Comment savoir si j’ai des containers en cours d’exĂ©cution ?
docker container lsComment savoir si j’ai des containers en cours d’exĂ©cution ?
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 3 hours buildx_buildkit_exciting_williams0Comment savoir si j’ai des containers en cours d’exĂ©cution ?
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 3 hours buildx_buildkit_exciting_williams0On obtient un tableau de tous les containers en cours d’exĂ©cution.
Il est possible de stopper un container.
docker container stop 399f3Il est possible de stopper un container.
docker container stop 399f3Pour redémarrer un container :
docker container start 399f3MĂȘme les đ.
Comment savoir si j’ai des containers stoppĂ©s ?
docker container ls --allComment savoir si j’ai des containers stoppĂ©s ?
docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
90725f661d4e hello-world "/hello" 13 seconds ago Exited (0) 12 seconds ago hardcore_wescoff
9d0a6586b9e1 busybox "echo hello world" 22 seconds ago Exited (0) 21 seconds ago gracious_moser
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while dâŠ" 6 minutes ago Up 5 minutes sweet_clarke
c036e57bbf05 jpetazzo/clock "/bin/sh -c 'while dâŠ" 17 minutes ago Exited (130) 17 minutes ago cool_euclid
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 4 hours buildx_buildkit_exciting_williams0Comment savoir si j’ai des containers stoppĂ©s ?
docker container ls --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
90725f661d4e hello-world "/hello" 13 seconds ago Exited (0) 12 seconds ago hardcore_wescoff
9d0a6586b9e1 busybox "echo hello world" 22 seconds ago Exited (0) 21 seconds ago gracious_moser
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while dâŠ" 6 minutes ago Up 5 minutes sweet_clarke
c036e57bbf05 jpetazzo/clock "/bin/sh -c 'while dâŠ" 17 minutes ago Exited (130) 17 minutes ago cool_euclid
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 3 hours ago Up 3 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 4 hours buildx_buildkit_exciting_williams0Avec le flag --all , on obtient un tableau de tous les containers quel que soit leur état.
Tout container stoppĂ© peut ĂȘtre supprimĂ©.
Tout container stoppĂ© peut ĂȘtre supprimĂ©.
docker container rm 90725f661d4eTout container stoppĂ© peut ĂȘtre supprimĂ©.
docker container rm 90725f661d4e
90725f661d4eTout container stoppĂ© peut ĂȘtre supprimĂ©.
docker container rm 90725f661d4e
90725f661d4eContainer "auto-nettoyant" đïž
Tout container stoppĂ© peut ĂȘtre supprimĂ©.
docker container rm 90725f661d4e
90725f661d4eContainer "auto-nettoyant" đïž
docker container run --interactive --tty --rm jpetazzo/clock




Sur un container en arriĂšre-plan
Sur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
Sur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
La commande suivante permet de lancer une commande Ă l’intĂ©rieur d’un container.
Sur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
La commande suivante permet de lancer une commande Ă l’intĂ©rieur d’un container.
docker container exec <containerID> echo "hello"Sur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
La commande suivante permet de lancer une commande Ă l’intĂ©rieur d’un container.
docker container lsSur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
La commande suivante permet de lancer une commande Ă l’intĂ©rieur d’un container.
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while dâŠ" 4 hours ago Up 4 hours sweet_clarke
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 7 hours ago Up 7 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 7 hours buildx_buildkit_exciting_williams0Sur un container en arriĂšre-plan
Il est possible d’interagir avec un container en arriĂšre-plan en cours d’exĂ©cution.
La commande suivante permet de lancer une commande Ă l’intĂ©rieur d’un container.
docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
368ed08a35e3 jpetazzo/clock "/bin/sh -c 'while dâŠ" 4 hours ago Up 4 hours sweet_clarke
02cbf2fb3721 cours-devops-docker-serve "/sbin/tini -g gulp âŠ" 7 hours ago Up 7 hours 0.0.0.0:8000->8000/tcp cours-devops-docker-serve-1
ebfbe695b2ec moby/buildkit:buildx-stable-1 "buildkitd" 2 months ago Up 7 hours buildx_buildkit_exciting_williams0
docker container exec 368ed08a35e3 echo hello
helloSur un container en arriĂšre-plan

docker container exec --interactive --tty <containerID> bashCa fonctionne aussi en interactif !
Ătapes
Lancer un container "daemon" jpetazzo/clock
Utiliser l’Ă©quivalent de tail -f pour lire la sortie standard du container đĄ
Lancer un second container "daemon" đč
Stocker l’identifiant de ce container dans une variable du shell, en une seule commande et en jouant avec docker container ls
Stopper le container avec cet identifiant
Afficher les containers lancĂ©s đđŠ
Afficher les containers arrĂȘtĂ©s đđŠ
# Lancer un container "daemon" `jpetazzo/clock`
docker container run --detach --name first-clock jpetazzo/clock
# Utiliser l'Ă©quivalent de `tail -f` pour lire la sortie standard du container đĄ
docker container logs -f first-clock
# * Lancer un second container "daemon" đč
docker container run --detach --name second-clock jpetazzo/clock
# Stocker l'identifiant de ce container dans une variable du shell, en une seule commande et en jouant avec `docker container ls`
# --filter "name=second-clock" filters the list of containers to include only those with the name "second-clock."
container_id=$(docker container ls -q --filter "name=second-clock")
# Stopper le container avec cet identifiant
docker container stop "$container_id"
# Afficher les containers lancĂ©s đđŠ
docker container ls
# Afficher les containers arrĂȘtĂ©s đđŠ
docker container ls --filter "status=exited"Relancer un des containers arrĂȘtĂ©s.
Exécuter un ps -ef dans ce container
Quel est le PID du process principal ?
Vérifier que le container "tourne" toujours
Supprimer l’image (tip : docker image rm)
Supprimer les containers
Supprimer l’image (pour de vrai cette fois)
# Relancer un des containers arrĂȘtĂ©s.
docker container start second-clock
# Exécuter un `ps -ef` dans ce container
docker container exec second-clock ps -ef
PID USER TIME COMMAND
1 root 0:00 /bin/sh -c while date; do sleep 1; done
56 root 0:00 sleep 1
57 root 0:00 ps -ef
# Quel est le PID du process principal ?
# 1
# Vérifier que le container "tourne" toujours
docker container ls
# (sortie abrégée : autres conteneurs et colonnes CREATED, PORTS retirés)
CONTAINER ID IMAGE COMMAND STATUS NAMES
edea0c46c6d8 jpetazzo/clock "/bin/sh -c 'while dâŠ" Up About a minute second-clock# Supprimer l'image (tip : `docker image rm`)
docker image rm jpetazzo/clock
Error response from daemon: conflict: unable to delete jpetazzo/clock:latest (must be forced)
- container edea0c46c6d8 is using its referenced image dc06bbc3744f
# Supprimer les containers (first-clock utilise aussi l'image)
docker container stop first-clock second-clock
first-clock
second-clock
docker container rm first-clock second-clock
first-clock
second-clock
# Supprimer l'image (pour de vrai cette fois)
docker image rm jpetazzo/clock
Untagged: jpetazzo/clock:latest
Deleted: sha256:dc06bbc3744f7200404bff0bbb2516925e7adea115e07de9da8b36bf15fe3dd3docker image rm -f ?Le message d’erreur suggĂšre de forcer. Voici ce que fait vraiment -f quand un conteneur utilise encore l’image :
docker image rm -f jpetazzo/clock
Untagged: jpetazzo/clock:latestSeule l'Ă©tiquette est retirĂ©e, que le conteneur soit arrĂȘtĂ© ou en cours d’exĂ©cution (il continue de tourner).
docker container ls --all affiche ensuite l’ID de l’image Ă la place de son nom.
Les couches restent sur le disque : l’espace n’est libĂ©rĂ© qu’aprĂšs suppression des conteneurs qui les utilisent.
ExĂ©cutez un conteneur, basĂ© sur l’image nginx en tĂąche de fond ("Background"), nommĂ© webserver-1
đĄ On parle de processus "dĂ©tachĂ©" (ou bien "dĂ©monisĂ©") đč
â ïž Pensez bien Ă docker container ls
Regardez le contenu du fichier /etc/os-release dans ce conteneur
đĄ docker container exec
Essayez d’arrĂȘter, dĂ©marrer puis redĂ©marrer le conteneur
â ïž Pensez bien Ă docker container ls Ă chaque fois
đĄ stop, start, restart
docker container run --detach --name=webserver-1 nginx
# <ID du conteneur>
docker container ls
docker container ls --all
docker container exec webserver-1 cat /etc/os-release
# ... Debian ...
docker container stop webserver-1
docker container ls
docker container ls --all
docker container start webserver-1
docker container ls
docker container ls --all
docker container start webserver-1
docker container lsVous savez désormais:
MaĂźtriser le cycle de vie des containers
Interagir avec les containers existants


Docker essaye de rĂ©soudre le problĂšme de l’empaquetage le plus "portable" possible
On n’en a pas encore vu les effets, ça arrive !
Vous avez vu qu’un containeur permet d’exĂ©cuter une commande dans un environnement "prĂ©parĂ©"
Catalogue d’images Docker par dĂ©faut : Le Docker Hub
Vous avez vu qu’on peut exĂ©cuter des conteneurs selon 3 modes :
"One shot"
Interactif
En tĂąche de fond
⇒ đ€ Mais comment ces images sont-elles fabriquĂ©es ? Quelle confiance leur accorder ?

Un conteneur est toujours exécuté depuis une image.
Une image de conteneur (ou "Image Docker") est un modĂšle ("template") d’application auto-suffisant.
⇒ Permet de fournir un livrable portable (ou presque).
C’est une collection de fichiers et de metadonnĂ©es.
C’est une collection de fichiers et de metadonnĂ©es.
C’est une suite de couches superposĂ©es.
C’est une collection de fichiers et de metadonnĂ©es.
C’est une suite de couches superposĂ©es.

C’est une collection de fichiers et de metadonnĂ©es.
C’est une suite de couches superposĂ©es.

Exemple d’une image Apache HTTPd "custom"

Exemple d’une image Apache HTTPd "custom"

Exemple d’une image Apache HTTPd "custom"

Exemple d’une image Apache HTTPd "custom"

Exemple d’une image Apache HTTPd "custom"

Exemple d’une image Apache HTTPd "custom"

"L’image est Ă la classe ce que le container est Ă l’objet"

đ€ Application Auto-Suffisante ?






Un simple fichier nommĂ© "Dockerfile" (majuscule sur le D et pas d’extension).

Un simple fichier nommĂ© "Dockerfile" (majuscule sur le D et pas d’extension).
C’est du texte, trĂšs pratique Ă stocker dans Git.

Un simple fichier nommĂ© "Dockerfile" (majuscule sur le D et pas d’extension).
C’est du texte, trĂšs pratique Ă stocker dans Git.
Une suite de clef-valeur.
âïž ProblĂšme :
cat /etc/os-release
# ...
git --version
# ...
# MĂȘme version de Linux que dans Codespaces
docker container run --rm ubuntu:24.04 git --version
# docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "git": executable file not found in $PATH: unknown.
# En interactif ?
docker container run --rm --tty --interactive ubuntu:24.04 git --versionBut : fabriquer une image Docker qui contient git
Dans votre Codespace, créez un dossier nommé docker-git/
Dans ce dossier, créer un fichier Dockerfile avec le contenu ci-dessous :
FROM alpine:3.24.2
RUN apk add --no-cache gitFabriquez votre image avec la commande docker image build --tag=docker-git chemin/vers/docker-git/
Testez l’image fraĂźchement fabriquĂ©e
đĄ docker image ls
cat <<EOF >Dockerfile
FROM alpine:3.24.2
RUN apk add --no-cache git
EOF
docker image build --tag=docker-git ./
docker image ls | grep docker-git
# Doit fonctionner
docker container run --rm docker-git:latest git --versionUn peu de cuisine…

đȘđ DĂFI
Créer une image alpine avec un JRE installé.
đđł RECETTE
On part d’une image Alpine
On installe un JRE
FROM alpine:3.24.2
LABEL maintainer="Tony Stark"
RUN apk add --no-cache openjdk17-jre-headless



docker image build -t myjava:1.42 .


Quand le build se termine, il se trouve dans le registre local.
docker image ls
REPOSITORY TAG IMAGE ID
myjava 1.42 d3017f59d5e2docker container run --interactive --tty myjava:1.42 sh
/ $/ $ java -version
openjdk version "17.0.8" 2023-07-18
OpenJDK Runtime Environment (build 17.0.8+7-alpine-r0)
OpenJDK 64-Bit Server VM (build 17.0.8+7-alpine-r0, mixed mode, sharing)
On les trouve oĂč, ces images ?
En local, on l’a vu.
Dans les registres Docker.
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
cours-devops-docker-serve latest 654fc65c0913 13 hours ago 668MB
cours-devops-docker-qrcode latest a3ff53e5d677 13 hours ago 668MB
cours-devops-docker-build latest a8b5055c6b8a 13 hours ago 668MB
jenkins/jenkins 2.504-slim-jdk17 2f264c295874 44 hours ago 669MB
jenkins/jenkins 2.504-slim 4db3d90f94da 44 hours ago 691MB
jenkins/jenkins 2.504-slim-jdk21 4db3d90f94da 44 hours ago 691MB
jenkins/jenkins 2.504 c6458a4308c9 44 hours ago 807MB
jenkins/jenkins 2.504-jdk21 c6458a4308c9 44 hours ago 807MB
jenkins/jenkins 2.504-jdk17 a44b80b5278d 44 hours ago 785MB
jenkins/jenkins 2.504-jdk25 df290633b1b1 44 hours ago 709MB
jenkins/jenkins 2.504-slim-jdk25 1b3e857e0e80 44 hours ago 593MB
jenkins/jenkins 2.504-rhel-ubi9-jdk21 5bf47807e933 44 hours ago 807MB
jenkins/jenkins 2.504-rhel-ubi9-jdk17 7c663bf36a0b 44 hours ago 785MB
jenkins/jenkins 2.504-rhel-ubi9-jdk25 7ade79f2d5d6 44 hours ago 709MB
jenkins/jenkins 2.504-alpine-jdk17 f8bb1d21d3b3 44 hours ago 458MB
jenkins/jenkins 2.504-alpine 9796a49cd5ad 44 hours ago 480MB
jenkins/jenkins 2.504-alpine-jdk21 9796a49cd5ad 44 hours ago 480MB
jenkins/jenkins 2.504-alpine-jdk25 713be11d8079 44 hours ago 384MB
tomcat 9.0 bfe8385d954f 4 days ago 608MB
tomcat latest 2b3894dce12e 4 days ago 607MB
img1758726197 latest 69b8e1bd3710 6 days ago 259MB
ubuntu latest 9cbed7541129 6 weeks ago 117MB
nginx latest d5f28ef21aab 6 weeks ago 279MB
hello-world latest 54e66cc1dd1f 7 weeks ago 20.3kB
alpine latest 4bcff63911fc 2 months ago 12.8MB
<none> <none> 8feb4d8ca535 5 months ago 109MB
moby/buildkit buildx-stable-1 14aa1b4dd92e 8 months ago 306MB
ghcr.io/jenkinsci/ssh-agents-plugin baseb2fb086 2bc6d82ca2b4 9 months ago 1.36GB
<none> <none> dfa54ef35e43 21 months ago 6.89MB
registry 2 a3d8aaa63ed8 24 months ago 37.2MB
jboss/wildfly latest 35320abafdec 3 years ago 1.17GBCe sont des plates-formes qui hébergent les images.
Il est possible de crĂ©er ses propres registres (ex : registre privĂ© d’entreprise)
Avant d’instancier un container, il faut rĂ©cupĂ©rer l’image en local.
Méthode explicite :
docker image pull mysqlMéthode implicite :
docker container run -d mysql$ docker image pull mysql
Using default tag: latest
latest: Pulling from library/mysql
5f70bf18a086: Pull complete
a734b0ff4ca6: Already exists
ec46eb0ce0a7: Pull complete
a74b383379bc: Pull complete
Digest: sha256:42dc1b67073f7ebab1...8c8d36c9031e408db0d
Status: Downloaded newer image for mysql:latestLes couches déjà présentes en local ne sont pas téléchargées de nouveau !




Une mĂȘme image peut avoir plusieurs noms et tags !
Le tag "latest" est réguliÚrement réaffecté sur les registres distants.
Plusieurs noms pour une signature
Une mĂȘme image peut avoir plusieurs noms et tags !
Le tag "latest" est réguliÚrement réaffecté sur les registres distants.
Plusieurs noms pour une signature
Tags disponibles pour MySQL

Pour rĂ©sumer…
[REGISTRY/][NAMESPACE/]NAME[:TAG|@DIGEST]Pas de Registre ? Défaut: registry.docker.com
Pas de Namespace ? Défaut: library
Pas de tag ? Valeur par défaut: latest
â ïž Friends don’t let friends use latest đ«
Digest: signature unique basée sur le contenu
ubuntu:22.04 ⇒ registry.docker.com/library/ubuntu:22.04
dduportal/docker-asciidoctor ⇒ registry.docker.com/dduportal/docker-asciidoctor:latest
ghcr.io/dduportal/docker-asciidoctor:1.3.2@sha256:xxxx
Rappel : â ïž Friends don’t let friends use latest đ«
Il est temps de "taguer" votre premiĂšre image !
docker image tag docker-git:latest docker-git:1.0.0Testez le fonctionnement avec le nouveau tag
Comparez les 2 images dans la sortie de docker image ls
docker image tag docker-git:latest docker-git:1.0.0
# 2 lignes
docker image ls | grep docker-git
# 1 ligne
docker image ls | grep docker-git | grep latest
# 1 ligne
docker image ls | grep docker-git | grep '1.0.0'
# Doit fonctionner
docker container run --rm docker-git:1.0.0 git --versionMettez Ă jour votre image en version 1.1.0 avec les changements suivants :
Ajoutez un LABEL dont la clef est description (et la valeur de votre choix)
Configurez git pour utiliser une branche main par défaut au lieu de master (commande git config --global init.defaultBranch main)
Indices :
đĄ Commande docker image inspect <image name>
đĄ Commande git config --get init.defaultBranch (dans le conteneur)
đĄ Ajoutez des lignes Ă la fin du Dockerfile
cat ./Dockerfile
FROM alpine:3.24.1
RUN apk add --no-cache git
LABEL description="Une image contenant git préconfiguré"
RUN git config --global init.defaultBranch main
docker image build -t docker-git:1.1.0 ./docker-git/
# Sending build context to Docker daemon 2.048kB
# Step 1/4 : FROM alpine:3.24.1
# ---> e40cf56b4be3
# Step 2/4 : RUN apk add --no-cache git
# ---> Using cache
# ---> 926b8d87f128
# Step 3/4 : LABEL description="Une image contenant git préconfiguré"
# ---> Running in 0695fc62ecc8
# Removing intermediate container 0695fc62ecc8
# ---> 68c7d4fb8c88
# Step 4/4 : RUN git config --global init.defaultBranch main
# ---> Running in 7fb54ecf4070
# Removing intermediate container 7fb54ecf4070
# ---> 2858ff394edb
Successfully built 2858ff394edb
Successfully tagged docker-git:1.1.0
docker container run --rm docker-git:1.0.0 git config --get init.defaultBranch
docker container run --rm docker-git:1.1.0 git config --get init.defaultBranch
# mainStep 2/4 : RUN apk add --no-cache git
---> Using cacheđ€ En fait, Docker n’a PAS exĂ©cutĂ© cette commande la seconde fois ⇒ ça va beaucoup plus vite !

đ Essayez de voir les layers avec dive. Il n’est pas installĂ© dans Codespaces, mais s’utilise en conteneur sans rien installer :
docker container run --rm --tty --interactive \
--volume /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest <image>:<tag>But : manipuler le cache d’images
Commencez par vĂ©rifier que le cache est utilisĂ© : relancez la derniĂšre commande docker image build (plusieurs fois s’il le faut)
Invalidez le cache en ajoutant le paquet APK make Ă installer en mĂȘme temps que git
â ïž Tag 1.2.0
Vérifiez que le cache est bien présent de nouveau
# Build one time
docker image build -t docker-git:1.1.0 ./docker-git/
# Second time is fully cached
docker image build -t docker-git:1.1.0 ./docker-git/
cat Dockerfile
# FROM alpine:3.24.1
# RUN apk add --no-cache git make
# LABEL description="Une image contenant git préconfiguré"
# RUN git config --global init.defaultBranch main
# Build one time
docker image build -t docker-git:1.2.0 ./docker-git/
# Second time is fully cached
docker image build -t docker-git:1.2.0 ./docker-git/
## Vérification
# Renvoie une erreur
docker container run --rm docker-git:1.1.0 make --version
# Doit fonctionner
docker container run --rm docker-git:1.2.0 make --versionDeux instructions permettent de définir la commande à lancer au démarrage du container.

Deux instructions permettent de définir la commande à lancer au démarrage du container.

Laquelle choisir ???
Cas d’usage : Ă©noncĂ© du besoin.
Besoin : je veux utiliser cURL mais il n’est pas prĂ©sent sur la machine hĂŽte.
Facile !
docker container run --rm curlimages/curl curl -x http://cache-etu.univ-artois.fr:3128 -L --connect-timeout 60 "http://google.com"
Cas d’usage. On va s’outiller!
FROM alpine:3.24
LABEL maintainer="John Doe"
RUN apk add --no-cache curl
# Si vous utilisez le proxy de l'Université
CMD ["curl", "-L", "--connect-timeout", "60", \
"-x", "http://cache-etu.univ-artois.fr:3128", \
"http://google.com"]
# Si vous utilisez votre propre proxy docker
# (sous Linux, lancer avec --add-host=host.docker.internal:host-gateway)
# CMD ["curl", "-L", "--connect-timeout", "60", \
# "-x", "http://host.docker.internal:3128", \
# "http://google.com"]docker image build -t my-curl:1.0 .docker container run --rm \
my-curl:1.0
% Total % Received [...]
100 84301 0 84301 [...]
<!doctype html><html [...]
[...]
[...]</script></body></html>
Cas d’usage.

Cas d’usage.


Cas d’usage: un cran plus loin.
L’image actuelle, c’est bien mais pas hyper flexible !
Et si on la rendait paramétrable ?
docker container run --rm my-curl:2.0 http://google.com
docker container run --rm my-curl:2.0 http://facebook.com
docker container run --rm my-curl:2.0 http://example.comCas d’usage, un cran plus loin
Paramétrable, et hop!
FROM alpine:3.24
LABEL maintainer="John Doe"
RUN apk add --no-cache curl
# Si vous utilisez le proxy de l'Université
ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
"-x", "http://cache-etu.univ-artois.fr:3128"]
# Si vous utilisez votre propre proxy docker
# (sous Linux, lancer avec --add-host=host.docker.internal:host-gateway)
# ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
# "-x", "http://host.docker.internal:3128"]docker image build -t my-curl:2.0 .docker container run --rm \
my-curl:2.0 http://google.com
% Total % Received [...]
100 84301 0 84301 [...]
<!doctype html><html [...]
[...]
[...]</script></body></html>Cas d’usage, un cran plus loin
Paramétrable, et hop!
FROM alpine:3.24
LABEL maintainer="John Doe"
RUN apk add --no-cache curl
# Si vous utilisez le proxy de l'Université
ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
"-x", "http://cache-etu.univ-artois.fr:3128"]
# Si vous utilisez votre propre proxy docker
# (sous Linux, lancer avec --add-host=host.docker.internal:host-gateway)
# ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
# "-x", "http://host.docker.internal:3128"]
docker image build -t my-curl:2.0 .docker container run --rm \
my-curl:2.0 http://google.com
% Total % Received [...]
100 84301 0 84301 [...]
<!doctype html><html [...]
[...]
[...]</script></body></html>Une URL par défaut, toujours remplaçable.
ENTRYPOINT : la commande.
CMD : ses arguments par défaut.
FROM alpine:3.24
LABEL maintainer="John Doe"
RUN apk add --no-cache curl
# Si vous utilisez le proxy de l'Université
ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
"-x", "http://cache-etu.univ-artois.fr:3128"]
# Si vous utilisez votre propre proxy docker
# (sous Linux, lancer avec
# --add-host=host.docker.internal:host-gateway)
# ENTRYPOINT ["curl", "-L", "--connect-timeout", "60", \
# "-x", "http://host.docker.internal:3128"]
CMD ["http://example.com"]docker image build -t my-curl:3.0 .docker container run --rm \
my-curl:3.0
% Total % Received [...]
100 713 0 713 [...]
<!doctype html><html [...]
[...]<title>Example Domain</title>docker container run --rm \
my-curl:3.0 http://neverssl.com
% Total % Received [...]
100 3961 100 3961 [...]
<html>
<head>
<title>NeverSSL [...]# Forme exec (JSON)
ENTRYPOINT ["curl", "-L", ...]
# Forme shell
ENTRYPOINT curl -L ...La forme shell passe par /bin/sh -c.
CMD et les arguments de run sont ignorés.
docker container run --rm \
my-curl:shell http://neverssl.com
curl: (2) no URL specifieddocker image inspect my-curl:shell \
--format '{{json .Config.Entrypoint}}'
["/bin/sh","-c","curl -L [...]"]RĂšgle : forme exec pour ENTRYPOINT et CMD.
Une image Docker fournit un environnement de systĂšme de fichier auto-suffisant (application, dĂ©pendances, binaries, etc.) comme modĂšle de base d’un conteneur
Les images Docker ont une convention de nommage permettant d’identifier les images trĂšs prĂ©cisĂ©ment
On peut spĂ©cifier une recette de fabrication d’image Ă l’aide d’un Dockerfile et de la commande docker image build
⇒ đ€ et si on utilisait Docker pour nous aider dans l’intĂ©gration continue ?
docker container run --interactive --tty httpd pwd
/usr/local/apache2Comment paramétrer le répertoire de travail de tous nos containers ?
FROM alpine:3.24
WORKDIR /repertoire/travail
RUN echo hello > ./world.txtFROM alpine:3.24
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
FROM alpine:3.24
WORKDIR /repertoire/travail
RUN echo hello > ./world.txt
FROM alpine:3.24
WORKDIR /repertoire/travail
RUN echo hello > ./world.txtdocker image build --tag mon-img .
...$ docker container run mon-img cat /repertoire/travail/world.txt
hello$ docker container run mon-img pwd
/repertoire/travail$ docker container run --workdir /home mon-img pwd
/homeCas d’usage.
ls
app.js$ docker container run --workdir /customdir node app.js
<error : app.js not found>$ docker container run --workdir /customdir --entrypoint ls node
<0 fichiers présents !>Comment fournir des fichiers à mes containers ?
Copie de fichiers.
FROM node:26-alpine
WORKDIR /app
COPY ./* /appdocker image build --tag mon-node .
...$ docker container run --workdir /app --entrypoint ls mon-node
Dockerfile
app.js$ docker container run mon-node app.js
Hello Mad Javascript World!Le mot clé ADD.
FROM image
ADD https://mon-nexus/foo/bar/1.0/package.tar /uncompressed/Il permet d’ajouter des fichiers aux images, tout comme COPY.
Il est capable de tĂ©lĂ©charger directement d’une URL
Il est capable de "untar" automatiquement.
On le retrouve dans d’anciens Dockerfile mais il tend Ă disparaĂźtre.
Oui, mais pas la terre entiĂšre !
docker image build --tag myjava:1.42 ./
Sending build context to Docker daemon 172.8MBOui, mais pas la terre entiĂšre !

Oui, mais pas la terre entiĂšre !

Oui, mais pas la terre entiĂšre !

Si un gros fichier traĂźne dans le dossier alors qu’il n’est mĂȘme pas utilisĂ© ni rĂ©fĂ©rencĂ© dans le Dockerfile, il sera tout de mĂȘme envoyĂ© au Docker daemon.
Oui, mais pas la terre entiĂšre !
"M’en fous ! j’ai une machine de fou et un rĂ©seau de fou ! YOLO !"
Et si la CI fait plusieurs builds par minute ?
Et si un vieux Keystore traĂźne dans les sources ?
.git/ ? .idea/ ? vraiment ?
Attention Ă l’invalidation de cache d’un step de build Ă cause de l’ajout d’un fichier inutile !
.dockerignore# ignore les dossiers .git et .cache
.git
.cache# ignore tous les fichiers *.class dans tous les dossiers
**/*.class# ignore les markdown sauf les README*.md (README-secret.md sera tout de mĂȘme ignorĂ© par contre)
*.md
!README*.md
README-secret.md



httpd:latest sera taggué en fedora/httpd:version1.0

Le bilan
Vous savez désormais:
Rédiger un Dockerfile
Nommer vos images
Créer vos outils

Le bilan
Vous savez désormais:
Rédiger un Dockerfile
Nommer vos images
Créer vos outils

La construction d’une image doit ĂȘtre automatisĂ©e.
Le fait de dĂ©lĂ©guer la construction des images permet d’ajouter toute une chaĂźne de traitement, de contrĂŽles des images pour s’assurer qu’elle respecte les rĂšgles RSSI.
patch management
droits sur le FS
user






diff --git a/src/Dockerfile b/src/Dockerfile
index c92c67cd48121705628f67a2af14b2a65e54cdfd..77919a581fffde7c57f43c8b7cbee2a8d84b628b 100644
--- a/src/Dockerfile
+++ b/src/Dockerfile
@@ -9,6 +9,10 @@ COPY start.sh /bin/start.sh
EXPOSE 4321
# This line opens port 4321 on the container. It's like installing a door in your house.
+RUN adduser --disabled-password --gecos "" --home /home/www www
+USER www
+WORKDIR /home/www
+
ENTRYPOINT ["/bin/start.sh"]
# This line sets the command that will be run when the container starts. It's like setting the alarm clock in your house.diff --git a/src/start.sh b/src/start.sh
index 44006d6cd8414706a5fcf564dbfd1e9c38d4bc0b..a6ed60fdf88759d4962685e7ceb6cdbc21867448 100755
--- a/src/start.sh
+++ b/src/start.sh
@@ -4,7 +4,7 @@
set -eux
# These are shell options. 'e' exits on error, 'u' treats unset variables as an error, and 'x' prints each command to the terminal. It's like the script's personal assistant, making sure everything runs smoothly.
-touch /home/www/started.time
+touch /tmp/started.time
# This creates a file named 'started.time' in the '/home/www' directory. It's like the script's way of marking its territory.
if [ $? -ne 0 ]; then
@@ -12,7 +12,7 @@ if [ $? -ne 0 ]; then
fi
# This checks the exit status of the last command. If it's not zero, which means there was an error, it exits the script. It's like the script's way of saying, "If I can't do it right, I won't do it at all."
-date > /home/www/started.time
+date > /tmp/started.time
# This writes the current date and time to the 'started.time' file. It's like the script's way of keeping a diary.
exec "$@"stages:
- "validator.sh"
- "docker scout"
variables:
PROXY: "http://cache-etu.univ-artois.fr:3128"
IMAGE: "devops-docker-tp05:latest"
validator-job:
image: cache-ili.univ-artois.fr/proxy_cache/library/docker
stage: "validator.sh"
before_script:
- export HTTP_PROXY="$PROXY"
- export HTTPS_PROXY="$PROXY"
- apk add -U bash
script:
- errors=$(./validator.sh)
- echo "$errors"
- exit $([ -z "$errors" ] && echo 0 || echo 1)
tags:
- docker2
# No login needed, using docker desktop with wsl
scout-job:
stage: "docker scout"
script:
- cd ./src
- docker build . -t "$IMAGE"
- docker scout cves --format only-packages --only-vuln-packages "$IMAGE"
- docker rmi "$IMAGE"
tags:
- myshellstages:
- run_script
- docker_scan
run_script:
stage: run_script
script:
- chmod +x validator.sh src/start.sh && ./validator.sh # Makes the scripts executable and runs validator.sh
- |
if [ $? -eq 0 ]; then
echo "Script exited successfully."
else
echo "Script exited with an error."
exit 1 # Mark the job as a failure
fi
docker_scan:
stage: docker_scan
script:
- IMG=$(echo img$$)
- docker image build --tag $IMG ./src > /dev/null
- echo "Will scan $IMG"
- echo $DOCKER_TOKEN | docker login -u $DOCKER_USERNAME --password-stdin
- docker scout cves --format only-packages --only-vuln-packages $IMG # Runs docker scout to scan the image
- docker scout recommendations $IMG # View base image update recommendations

Ne peut-on pas trouver plus 'user-friendly'?
Humour de g33k

docker container run --detach --name my-web httpdNom du container explicite
docker container logs my-webdocker container exec --interactive --tty my-web shAttention, spoiler pour les n00bs de DC Comics
docker container rename clark_kent superman



docker container inspect my-web
[
{
"Id": "0ac8cdf8d447c6d316a04bd1a7f74cd2677eea3478f11f0be5241c1bb2d4c7da",
"Created": "2023-10-24T19:40:11.84788911Z",
"Path": "httpd-foreground",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 3275,
"ExitCode": 0,
"Error": "",
"StartedAt": "2023-10-24T19:40:12.363841649Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
...
]JQ est le meilleur outil pour parser du JSON dans le shell
docker container inspect my-web | jq .Les templates de GO peuvent ĂȘtre utilisĂ©s directement
docker container inspect --format '{{json .Created }}' my-webĂtapes:
Lancer un container nginx
Trouver la syntaxe permettant d’extraire le "CMD" qui a Ă©tĂ© passĂ© au dĂ©marrage du container
Trouver une seconde mĂ©thode pour faire la mĂȘme chose
On démarre un container nginx avec la commande
# Here we're using the 'docker container run' command to start a new Docker container.
# The '--detach' option tells Docker to run the container in the background and print the container ID.
# The '--name' option allows us to give our container a custom name, in this case 'my-web'.
# Finally, 'nginx' is the name of the Docker image we want to use to create the container.
# So, to sum up, this command will start a new Docker container in the background, using the 'nginx' image, and name it 'my-web'.
docker container run --detach --name my-web nginxUne solution facile est de faire:
# In this command, we're using 'docker container inspect' to get detailed information about our 'my-web' container.
# The '--format' option allows us to specify the output format. Here, we're using the Go template '{{json .Config.Cmd }}' to get the command used to start the container in JSON format.
# We then pipe this JSON output to 'jq', a powerful command-line JSON processor.
# The '.' in 'jq .' tells 'jq' to take the input JSON and output it as is, effectively pretty-printing it.
# So, to sum up, this command will give us the command used to start the 'my-web' container, in a pretty-printed JSON format.
docker container inspect --format '{{json .Config.Cmd }}' my-web | jq .Ăa nous donne:
[
"nginx",
"-g",
"daemon off;"
][
"nginx",
"-g",
"daemon off;"
]Qu’on pourrait nettoyer pour avoir nginx -g daemon off; avec:
# This command is a symphony in three parts. First, we're using 'docker container inspect' to get detailed information about our 'my-web' container.
# The '--format' option allows us to specify the output format. Here, we're using the Go template '{{json .Config.Cmd }}' to get the command used to start the container in JSON format.
# We then pipe this JSON output to 'jq', a powerful command-line JSON processor. The '-r' option tells 'jq' to output raw strings instead of JSON-encoded strings, and '.[]' tells 'jq' to output the elements of the array.
# But we're not done yet. We then pipe this output to 'tr', which replaces the newlines with spaces, giving us a neat, single-line command.
# So, to sum up, this command will give us the command used to start the 'my-web' container, as a single line of text.
docker container inspect --format '{{json .Config.Cmd }}' my-web | jq -r '.[]' | tr '\n' ' 'qui donne nginx -g daemon off;.
Une autre solution serait:
# This command is using 'docker container ls' to list our Docker containers. The '--filter' option allows us to only show containers with the name 'my-web', and '--no-trunc' prevents Docker from truncating the output.
# We then pipe this output to 'awk', a powerful text processing tool. 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")}' tells 'awk' to find the positions of the 'COMMAND' and 'CREATED' headers in the first line of the output.
# 'NR>1 {print substr($0, cmd, created-cmd)}' then tells 'awk' to print the substring from the start of the 'COMMAND' field to the start of the 'CREATED' field for the rest of the lines.
# So, to sum up, this command will give us the command used to start the 'my-web' container, extracted from the output of 'docker container ls'.
docker container ls --filter "name=my-web" --no-trunc | awk 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")} NR>1 {print substr($0, cmd, created-cmd)}'# This command is using 'docker container ls' to list our Docker containers. The '--filter' option allows us to only show containers with the name 'my-web', and '--no-trunc' prevents Docker from truncating the output.
# We then pipe this output to 'awk', a powerful text processing tool. 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")}' tells 'awk' to find the positions of the 'COMMAND' and 'CREATED' headers in the first line of the output.
# 'NR>1 {print substr($0, cmd, created-cmd)}' then tells 'awk' to print the substring from the start of the 'COMMAND' field to the start of the 'CREATED' field for the rest of the lines.
# So, to sum up, this command will give us the command used to start the 'my-web' container, extracted from the output of 'docker container ls'.
docker container ls --filter "name=my-web" --no-trunc | awk 'NR==1 {cmd=index($0, "COMMAND"); created=index($0, "CREATED")} NR>1 {print substr($0, cmd, created-cmd)}'qui donnerait "/docker-entrypoint.sh nginx -g 'daemon off;'".

docker container run --detach nginx
bd12c4d7110d17ce80...docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS
bd12c4d71 nginx "/docker-entrypoint.âŠ" 35 s. ago Up 33 s. 80/tcpOk, mon container expose un service sur le port TCP 80, mais j’appelle comment mon Nginx ?
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "c0fcec43e3e8âŠeecd6558ac0870a468a3",
"EndpointID": "0ec8fb237a10e9227359b4âŠdb23edc32",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}

docker container inspect \
--format '{{range .NetworkSettings.Networks}}{{println .IPAddress}}{{end}}' <ContainerID>L’ancienne forme, encore dans beaucoup de tutoriels, Ă©choue avec Docker 29 :
docker container inspect --format "{{.NetworkSettings.IPAddress}}" <ContainerID>
template parsing error: [...] map has no entry for key "IPAddress"L’adresse n’est plus que sous .NetworkSettings.Networks.<rĂ©seau>, d’oĂč le range.
curl -I --noproxy '*' http://172.17.0.2:80
HTTP/1.1 200 OK
....
Server: nginx/1.25 ...Il est maintenant facile d’interagir avec notre serveur web !



curl -I --noproxy '*' http://172.17.0.2:80curl -I --noproxy '*' http://172.17.0.2:80
HTTP/1.1 200 OK
Server: nginx/1.25.2
Date: Wed, 18 Oct 2023 11:54:49 GMT
Content-Type: text/html
Content-Length: 284237
Last-Modified: Tue, 10 Oct 2023 12:12:13 GMT
Connection: keep-alive
ETag: "65253f9d-4564d"
Accept-Ranges: bytes$ curl -I --noproxy '*' http://localhost:8000
HTTP/1.1 200 OK
Server: nginx/1.25.2
Date: Wed, 18 Oct 2023 11:57:25 GMT
Content-Type: text/html
Content-Length: 284237
Last-Modified: Tue, 10 Oct 2023 12:12:13 GMT
Connection: keep-alive
ETag: "65253f9d-4564d"
Accept-Ranges: bytesĂtapes:
CrĂ©er un fichier HTML et le distribuer Ă partir d’un container nginx
Récupérer un war sur GitHub et le déployer dans un container tomcat (https://github.com/gounthar/tp07-sample/releases/download/v2026.09.07-083440/sample-2026.09.07-083440.war)
RĂ©cupĂ©rer le code sur https://spring.io/guides/gs/spring-boot/ et faire tourner l’appli (sous rĂ©pertoire complete)
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>docker container run --name my-nginx -v /fully/qualified/path/on/my/computer/:/usr/share/nginx/html:ro -d -p 8000:80 nginx:1.31.6-alpine3.24curl --noproxy '*' http://localhost:8000
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>wget -O sample.war https://github.com/gounthar/tp07-sample/releases/download/v2026.09.07-083440/sample-2026.09.07-083440.war
# wget sort en 0 mĂȘme s'il a reçu une page HTML : vĂ©rifier que c'est bien une archive
file sample.war
sample.war: Zip archive data, made by v2.0 UNIX, ...# Used to run a Docker container with the name "tomcat-container" using the "tomcat:11.0.26-jdk25-temurin-noble" image. The container is
# configured to expose port 8080 on the host machine, which will be mapped to the corresponding port inside
# the container. Additionally, a volume is mounted to the container, linking the
# "/fully/qualified/path/on/my/computer/sample.war" file to "/usr/local/tomcat/webapps/sample.war"
# path within the container. This allows the Tomcat server running inside the container to access and deploy the
# "sample.war" file. The "-d" flag is used to run the container in detached mode, meaning it will run in the
# background.
docker container run -d -p 8080:8080 --name tomcat-container -v /fully/qualified/path/on/my/computer/sample.war:/usr/local/tomcat/webapps/sample.war tomcat:11.0.26-jdk25-temurin-nobleVisitez avec votre navigateur http://localhost:8080/sample/.
git clone https://github.com/spring-guides/gs-spring-boot.gitC’est un dĂ©but…
cd gs-spring-boot/completemvn packagejava -jar target/spring-boot-complete-0.0.1-SNAPSHOT.jarOk, il y a de l’idĂ©e…
DerriĂšre le proxy de l’universitĂ©, mvn package Ă©choue : Maven ignore HTTP_PROXY et HTTPS_PROXY.
Il lit son proxy dans ~/.m2/settings.xml.
<!-- Proxy de l'université pour Maven, qui ignore HTTP_PROXY et HTTPS_PROXY -->
<settings>
<proxies>
<proxy>
<id>univ</id>
<active>true</active>
<protocol>http</protocol>
<host>cache-etu.univ-artois.fr</host>
<port>3128</port>
</proxy>
</proxies>
</settings>mkdir -p ~/.m2 && cp settings.xml ~/.m2/settings.xmlDans un Dockerfile, on le monte comme secret de build, le temps du seul RUN mvn package : il ne reste pas dans l’image.
Sans --secret (hors de l’universitĂ©), le montage est simplement absent.
Assemblons tout ça dans un Dockerfile.
# Use a Maven image for building the application
FROM maven:3.9-eclipse-temurin-25-alpine
WORKDIR /app
# Clone the Spring Boot application repository
RUN apk add git && git clone https://github.com/spring-guides/gs-spring-boot.git
# Change directory to the application's complete directory
WORKDIR /app/gs-spring-boot/complete
# Build the application using Maven; settings.xml (proxy) is mounted as a build secret, if provided
RUN --mount=type=secret,id=maven-settings,target=/root/.m2/settings.xml mvn package
WORKDIR /app/gs-spring-boot/complete/target
# Command to run the Spring Boot application
CMD ["java", "-jar", "spring-boot-complete-0.0.1-SNAPSHOT.jar"]Spoiler alert: le nom de fichier donne une indication…
docker image build --secret id=maven-settings,src=settings.xml \
-t spring-boot-app --file Dockerfile.ugly .Et logiquement…
docker container run -d -p 8080:8080 --name spring-boot-container spring-boot-app
Ăa fonctionne, mais c’est comme si je laissais la grue dans la chambre une fois que j’avais fini de construire ma maison !
On essaye un Dockerfile moins BTP?
# Use a Maven image for building the application
FROM maven:3.9-eclipse-temurin-25-alpine AS build
WORKDIR /app
# Clone the Spring Boot application repository
RUN apk add git && git clone https://github.com/spring-guides/gs-spring-boot.git
# Change directory to the application's complete directory
WORKDIR /app/gs-spring-boot/complete
# Build the application using Maven; settings.xml (proxy) is mounted as a build secret, if provided
RUN --mount=type=secret,id=maven-settings,target=/root/.m2/settings.xml mvn package
# Use a lightweight Java image for running the application
FROM eclipse-temurin:25-jre-alpine
WORKDIR /app
# Copy the built JAR file from the previous build stage
COPY --from=build /app/gs-spring-boot/complete/target/spring-boot-complete-0.0.1-SNAPSHOT.jar ./
# Command to run the Spring Boot application
CMD ["java", "-jar", "spring-boot-complete-0.0.1-SNAPSHOT.jar"]Le multistage, c’est la classe Ă Arras. On en reparle aprĂšs.
Avec un autre exemple… Une image pour construire l’appli:
FROM golang:1.27
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .Une image pour faire tourner l’appli
FROM alpine:3.24.2
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]Avec un autre exemple
#!/bin/sh
echo Building alexellis2/href-counter:build
docker image build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \
-t alexellis2/href-counter:build . -f Dockerfile.build
docker container create --name extract alexellis2/href-counter:build
docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app
docker container rm -f extract
echo Building alexellis2/href-counter:latest
docker image build --no-cache -t alexellis2/href-counter:latest .
rm ./appAvec un autre exemple

Avec un autre exemple

Avec un autre exemple

Avec un autre exemple


FROM golang:1.27
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .
FROM alpine:3.24.2
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]FROM golang:1.27
WORKDIR /go/src/github.com/alexellis/href-counter/
COPY go.mod .
COPY go.sum .
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-s -w" -o app .
FROM alpine:3.24.2
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]On copie les fichiers depuis le premier stage
Le bilan : achievement unlocked
Vous savez désormais:
MaĂźtriser le cycle de vie des containers
Interagir avec les containers existants
Nommer les containers
Inspecter les containers
Appeler les containers
Obtenir des containers légers




Partage d’un rĂ©pertoire de votre systĂšme hĂŽte avec un conteneur Docker.
Utile pour partager des fichiers ou des données de configuration avec un conteneur.
Données stockées sur le systÚme hÎte, mais accessibles depuis le conteneur.
docker container run -v /chemin/du/systeme/hote:/chemin/dans/le/conteneur
Stockage persistant ⇒ donnĂ©es en dehors du systĂšme de fichiers du conteneur.
Utile pour stocker des donnĂ©es qui doivent survivre Ă l’arrĂȘt ou Ă la suppression d’un conteneur.
Volumes indépendants du cycle de vie du conteneur.
docker container run -v nom_du_volume:/chemin/dans/le/conteneur
SystÚme de fichiers temporaire stocké en RAM.
Utile lorsque des donnĂ©es temporaires doivent ĂȘtre stockĂ©es en mĂ©moire pour des performances Ă©levĂ©es.
Les donnĂ©es n’existent que tant que le conteneur est en cours d’exĂ©cution.
docker container run --tmpfs /chemin/dans/le/conteneur

"Créer une image avec les sources de mon application web juste pour distribuer des ressources statiques via Apache ! Merci Docker !"
Un étudiant excédé

Partage de dossier

Partage de fichier

| Le chemin dans la machine hĂŽte doit ĂȘtre un chemin absolu ! |
| L’exemple ci-dessus ne peut donc pas fonctionner |

Il est possible de crĂ©er des "espaces mĂ©moire" que l’on peut mettre Ă disposition des containers.
docker volume create --name my-datadocker container run -v my-data:/data busybox ls /data| my-data n’est plus un chemin absolu, mais juste le nom du volume |
Lister tous les volumes
docker volume lsSupprimer un volume
docker volume rm my-dataInspecter un volume
docker volume inspect my-dataFROM alpine:3.24
WORKDIR /repertoire/travail
VOLUME ["/data"]
RUN echo hello > ./world.txtCrĂ©ation d’un volume anonyme mappĂ© sur le rĂ©pertoire /data des containers basĂ©s sur cette image
docker container run \
-d \
--read-only \
--tmpfs /usr/local/apache2/logs \
--tmpfs /tmp \
httpd| Les donnĂ©es sont perdues Ă l’arrĂȘt du container. |

| Les donnĂ©es sont perdues Ă l’arrĂȘt du container. |

| Les donnĂ©es sont perdues Ă l’arrĂȘt du container. |
CrĂ©ation d’images
Création des containers
cycle de vie
nommage
débug
réseau
volumes
CrĂ©ation d’images â
CrĂ©ation des containers â
cycle de vie â
nommage â
dĂ©bug â
rĂ©seau â
volumes â
Lancer un container nginx et lui faire distribuer une page web externe au container.

En utilisant un montage de répertoire (Bind Mount) :
Utilisez un montage de rĂ©pertoire pour mapper un rĂ©pertoire de l’hĂŽte vers le conteneur Nginx.
Placez vos fichiers HTML dans un répertoire sur votre machine hÎte, puis mappez ce répertoire vers le conteneur Nginx.
docker container run -d -p 80:80 --name mon-nginx -v /chemin/vers/la/page/web/sur/lhote:/usr/share/nginx/html nginxRemplacez /chemin/vers/la/page/web/sur/lhote par le chemin rĂ©el vers le rĂ©pertoire contenant vos fichiers de la page web sur l’hĂŽte.
En utilisant un volume nommé (Named Volume) :
Créez un volume nommé et copiez vos fichiers de la page web dedans.
Montez ce volume nommé dans le conteneur Nginx.
Tout d’abord, crĂ©ez un volume nommĂ© :
docker volume create mon-nginx-htmlCopiez vos fichiers de la page web dans ce volume :
# Copie le *contenu* du dossier hÎte (monté sur /html) dans le volume (monté sur /cible).
# "/html/." et non "/html", sinon les fichiers atterrissent dans un sous-répertoire.
docker container run --rm \
--volume mon-nginx-html:/cible \
--volume /chemin/vers/la/page/web/sur/lhote:/html \
--workdir /cible \
busybox cp -r /html/. .Maintenant, lancez le conteneur Nginx et montez le volume nommé :
docker container run -d -p 80:80 --name mon-nginx -v mon-nginx-html:/usr/share/nginx/html nginxĂtapes:
Créer un volume nommé "data"
Démarrer un container attaché à ce volume
Lancer un processus qui écrit un fichier dans le volume
Démarrer un second container attaché à ce volume
Lire les données du volume depuis le second container
Supprimer le volume
Créer un volume nommé "data" :
docker volume create dataDémarrer un premier conteneur attaché à ce volume :
docker container run -it --rm --name premier-container -v data:/donnees busyboxDans le premier conteneur, lancez un processus qui écrit un fichier dans le volume (par exemple, un fichier texte) :
echo "Données du premier conteneur" > /donnees/mon-fichier.txtQuittez le premier conteneur.
DĂ©marrer un second conteneur attachĂ© au mĂȘme volume :
docker container run -it --rm --name second-container -v data:/donnees busyboxDans le second conteneur (dĂ©marrĂ© Ă l’Ă©tape prĂ©cĂ©dente), lisez les donnĂ©es du volume (le fichier texte) :
cat /donnees/mon-fichier.txtVous devriez voir le contenu du fichier affichĂ© Ă l’Ă©cran.
Quittez le second conteneur.
Vous pouvez maintenant supprimer le volume "data" car vous n’en avez plus besoin :
docker volume rm dataĂcrire un Dockerfile qui:
Ajoute des fichiers dans un dossier
Déclare ce dossier comme volume anonyme
Ajoute d’autres fichiers dans ce dossier
Que verra-t-on dans ce dossier au sein de nos containers ?
Tout d’abord, crĂ©ez un rĂ©pertoire pour votre projet et placez-vous dedans.
Créez un Dockerfile avec le contenu suivant :
FROM alpine:3.24
# Ajoutez des fichiers dans un dossier
RUN mkdir /mon-dossier
RUN echo "Contenu du fichier 1" > /mon-dossier/fichier1.txt
RUN echo "Contenu du fichier 2" > /mon-dossier/fichier2.txt
# Déclarez ce dossier comme un volume anonyme
VOLUME ["/mon-dossier"]
# Ajoutez d'autres fichiers dans ce dossier
RUN echo "Nouveau contenu du fichier 3" > /mon-dossier/fichier3.txtEnsuite, vous pouvez crĂ©er une image Docker Ă partir de ce Dockerfile en exĂ©cutant la commande suivante dans le mĂȘme rĂ©pertoire que le Dockerfile :
docker image build -t mon-image .docker image build -t mon-image .Assurez-vous que "mon-image" est le nom que vous souhaitez donner Ă votre image Docker. AprĂšs avoir créé l’image, vous pouvez exĂ©cuter un conteneur basĂ© sur cette image :
docker container run -it mon-imageLorsque vous ĂȘtes dans le conteneur, accĂ©dez au dossier /mon-dossier :
cd /mon-dossierVous verrez les fichiers "fichier1.txt", "fichier2.txt" et "fichier3.txt" dans ce dossier.
lsfichier1.txt fichier2.txt fichier3.txtcat *Contenu du fichier 1
Contenu du fichier 2
Nouveau contenu du fichier 3cat *Contenu du fichier 1
Contenu du fichier 2
Nouveau contenu du fichier 3Vous devriez voir la liste des fichiers et leurs contenus.
fichier3.txt est lĂ : avec BuildKit, le constructeur par dĂ©faut, un RUN Ă©crit dans le dossier mĂȘme aprĂšs VOLUME.
Ce n’est pas vrai de tous les constructeurs : voir la diapositive suivante.
Le mĂȘme Dockerfile, construit avec l’ancien constructeur (dĂ©prĂ©ciĂ©) :
DOCKER_BUILDKIT=0 docker image build -t mon-image-legacy .
docker container run --rm mon-image-legacy ls /mon-dossierfichier1.txt
fichier2.txtLe RUN qui suit VOLUME a été exécuté, mais ses modifications du dossier ont été jetées.
La documentation le dit : modifications Ă©cartĂ©es avec l’ancien constructeur, conservĂ©es avec BuildKit.
Pour ne pas dépendre du constructeur, placez VOLUME aprÚs les instructions qui remplissent le dossier. |
Interagir avec le Docker ENGINE






Sauf que…


docker container run -d -p 8000:80 nginx

curl -I --noproxy '*' http://172.17.0.2:80
HTTP/1.1 200 OK
...curl -I --noproxy '*' http://localhost:8000
HTTP/1.1 200 OK
...docker network lsNETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge local
docker network lsNETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge local
docker network inspect bridge[
{
"Name": "bridge",
"Id": "11b7af7b16e4cf9fe42733aa1b6900ed876407e3b55e692c9dfe03505e2af19f",
"Created": "2023-10-31T15:08:44.054140179Z",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
...docker network create mon-reseaudocker network lsNETWORK ID NAME DRIVER SCOPE
11b7af7b16e4 bridge bridge local
1112832d205b cours-devops-docker_default bridge local
7ab15cc28199 docker_volumes-backup-extension-desktop-extension_default bridge local
34caf4674478 host host local
46953dc9b3d5 mon-reseau bridge local
2a179c7be3b3 none null local
0c577a792771 portainer_portainer-docker-extension-desktop-extension_default bridge localdocker container run -d --net=mon-reseau --name=app imgIl est opportun de nommer un container quand on l’attache Ă un rĂ©seau.
Docker fournit un DNS interne dans les rĂ©seaux custom. Les containers d’un mĂȘme rĂ©seau peuvent se "voir" et "s’appeler" par leur noms.
docker network create mynetdocker container run -d --name web --net mynet nginxdocker container run --net mynet alpine ping webPING web (172.21.0.2): 56 data bytes
64 bytes from 172.21.0.2: seq=0 ttl=64 time=0.442 ms
64 bytes from 172.21.0.2: seq=1 ttl=64 time=0.105 ms
64 bytes from 172.21.0.2: seq=2 ttl=64 time=0.099 ms
64 bytes from 172.21.0.2: seq=3 ttl=64 time=0.150 ms
64 bytes from 172.21.0.2: seq=4 ttl=64 time=0.114 ms
64 bytes from 172.21.0.2: seq=5 ttl=64 time=0.098 ms
64 bytes from 172.21.0.2: seq=6 ttl=64 time=0.099 ms
64 bytes from 172.21.0.2: seq=7 ttl=64 time=0.100 ms
64 bytes from 172.21.0.2: seq=8 ttl=64 time=0.114 ms
64 bytes from 172.21.0.2: seq=9 ttl=64 time=0.097 ms
^C
--- web ping statistics ---
10 packets transmitted, 10 packets received, 0% packet loss
round-trip min/avg/max = 0.097/0.141/0.442 ms
NGINX, contrairement Ă d’autres outils (curl, wget, applications), rĂ©sout les noms Ă©crits en dur (bloc upstream, proxy_pass http://nom:port) une seule fois, au chargement de la configuration.
Si le service amont n’est pas encore dĂ©marrĂ© â NGINX Ă©choue au dĂ©marrage : c’est un problĂšme d'ordre de dĂ©marrage
Si un conteneur redĂ©marre et change d’IP â NGINX continue d’envoyer vers l'ancienne IP, mĂȘme si un autre conteneur l’a rĂ©cupĂ©rĂ©e
Erreur typique : host not found in upstream "nom-service:port"
events {
worker_connections 1024;
}
http {
# â AUCUNE directive resolver !
upstream backend {
server app-server:8080; # â ĂCHEC au dĂ©marrage !
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}Résultat : nginx: [emerg] host not found in upstream "app-server:8080"
Ajouter resolver 127.0.0.11; Ă cette configuration ne change rien : mĂȘme message, mĂȘme arrĂȘt.
resolver + variableevents {
worker_connections 1024;
}
http {
# DNS interne Docker pour résolution dynamique
resolver 127.0.0.11 valid=30s ipv6=off;
server {
listen 80;
location / {
# â
Utiliser une variable pour forcer la résolution dynamique
set $backend_host "app-server:8080";
proxy_pass http://$backend_host;
}
}
}
Deux façons de passer Ă la rĂ©solution Ă l’exĂ©cution :
|
resolvehttp {
resolver 127.0.0.11 valid=30s ipv6=off;
upstream backend {
zone backend 64k; # obligatoire avec resolve
server app-server:8080 resolve;
}
# server { ... proxy_pass http://backend; ... }
}NGINX dĂ©marre mĂȘme si app-server n’existe pas encore (502 en attendant)
Le nom est rĂ©-interrogĂ© Ă l’Ă©chĂ©ance de valid : jusqu’Ă 30 s de 502 aprĂšs l’arrivĂ©e du service
L’approche variable, elle, rĂ©pond dĂšs la requĂȘte suivante
resolver ?| Situation | resolver ? | Raison |
|---|---|---|
| â SANS EFFET | RĂ©solu au chargement, |
| â OBLIGATOIRE | RĂ©solution Ă l’exĂ©cution |
| â OBLIGATOIRE | RĂ©solution Ă l’exĂ©cution (+ |
| â NON | Pas de rĂ©solution DNS |
resolverresolver 127.0.0.11 valid=30s ipv6=off;
# ^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^
# Adresse DNS Cache TTL DĂ©sactiver IPv6127.0.0.11 : DNS interne Docker (sur les rĂ©seaux dĂ©finis par l’utilisateur, dont ceux de Compose)
valid=30s : Durée de cache imposée (sans valid, NGINX suit le TTL de la réponse DNS)
ipv6=off : Désactive IPv6 (optionnel, évite warnings)
Le rĂ©seau bridge par dĂ©faut n’a pas ce DNS : crĂ©er un rĂ©seau avec docker network create, ou laisser Compose le faire. |
Créez un projet avec :
NGINX (proxy) vers webapp
webapp (serveur web simple, port 80 dans le conteneur, 8080 sur l’hĂŽte)
services:
webapp:
image: nginx:1.31.6-alpine3.24
ports:
- "8080:80"
proxy:
image: nginx:1.31.6-alpine3.24
ports:
- "8000:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:roTest 1 : bloc upstream vers webapp:80, sans resolver. DĂ©marrer le proxy seul (docker compose up proxy) â Observer l’Ă©chec NGINX
Test 1 bis : ajouter resolver 127.0.0.11; au test 1 â MĂȘme Ă©chec, mĂȘme message
Test 2 : resolver 127.0.0.11 et proxy_pass par variable (diapositive « resolver + variable »). Le proxy dĂ©marre seul, puis rĂ©pond dĂšs que webapp tourne â SuccĂšs !
RĂšgle d’or : un nom Ă©crit en dur est rĂ©solu au chargement. Pour rĂ©soudre Ă l’exĂ©cution : variable ou |
Avec un nom écrit en dur,
|
docker network connect mynet myappCette commande permet d’attacher un container Ă un rĂ©seau aprĂšs sa crĂ©ation.
Ătapes:
Lister et inspecter les réseaux existants
Lancer un container nginx nommé "web1"
A quel réseau est-il attaché ?
CrĂ©er un rĂ©seau de type bridge et l’inspecter
Lancer un container nginx nommĂ© "web2" et l’attacher au rĂ©seau créé
L’inspection confirme-t-elle l’attachement ?
Lancer un bash dans le container "web1"
Est-ce que "web1" peut voir "web2" ?
Corriger pour que ce soit le cas
Ătape 1 : Lister et inspecter les rĂ©seaux existants
# Liste tous les réseaux Docker existants
docker network ls
# Inspecte un rĂ©seau spĂ©cifique (par exemple, le rĂ©seau bridge, d'autres rĂ©seaux peuvent ĂȘtre inspectĂ©s)
docker network inspect bridgeĂtape 2 : Lancer un container "nginx" nommĂ© "web1"
# Lance un conteneur "nginx" nommé "web1"
docker container run --name web1 -d nginxĂtape 3 : VĂ©rifier le rĂ©seau auquel "web1" est attachĂ©
# RécupÚre l'identifiant de réseau complet pour le container "web1"
network_id=$(docker container inspect -f '{{.NetworkSettings.Networks.bridge.NetworkID}}' web1)
# Raccourcit l'identifiant du réseau aux premiers 12 caractÚres
shortened_network_id=$(echo $network_id | cut -c 1-12)
# Liste tous les réseaux docker, en filtrant par l'identifiant du réseau
docker network ls --format "table {{.ID}}\t{{.Name}}" \
| awk -v network_id="$shortened_network_id" '$1 == network_id {print $2}'Ătape 3 : VĂ©rifier le rĂ©seau auquel "web1" est attachĂ©
# RécupÚre l'identifiant de réseau complet pour le container "web1"
network_id=$(docker container inspect -f '{{.NetworkSettings.Networks.bridge.NetworkID}}' web1)
# Raccourcit l'identifiant du réseau aux premiers 12 caractÚres
shortened_network_id=$(echo $network_id | cut -c 1-12)
# Liste tous les réseaux docker, en filtrant par l'identifiant du réseau
docker network ls --format "table {{.ID}}\t{{.Name}}" \
| awk -v network_id="$shortened_network_id" '$1 == network_id {print $2}'bridgeĂtape 4 : CrĂ©er un rĂ©seau de type bridge et l’inspecter
# Crée un réseau Docker de type bridge nommé "mon-reseau"
docker network create mon-reseau
# Inspecte le réseau "mon-reseau"
docker network inspect mon-reseauĂtape 5 : Lancer un container "nginx" nommĂ© "web2" et l’attacher au rĂ©seau créé
# Lance un conteneur "nginx" nommé "web2" et l'attache au réseau "mon-reseau"
docker container run --name web2 -d --network mon-reseau nginxĂtape 5 : Lancer un container "nginx" nommĂ© "web2" et l’attacher au rĂ©seau créé
# Lance un conteneur "nginx" nommé "web2" et l'attache au réseau "mon-reseau"
docker container run --name web2 -d --network mon-reseau nginxĂtape 6 : VĂ©rifier l’attachement du container "web2" au rĂ©seau
# Inspecte le conteneur "web2" pour vérifier le réseau auquel il est attaché
docker container inspect web2 | grep NetworkModeĂtape 7 : Lancer un bash dans le container "web1"
# Lance un shell interactif dans le conteneur "web1"
docker container exec -it web1 bashĂtape 8 : VĂ©rifier si "web1" peut voir "web2"
# Ă l'intĂ©rieur du conteneur "web1," essayez de faire une requĂȘte HTTP vers "web2"
# --noproxy : web2 est un nom interne, la requĂȘte ne doit pas partir vers le proxy
curl --noproxy '*' web2curl: (6) Could not resolve host: web2Ătape 9 : Corriger pour permettre la communication entre "web1" et "web2"
# Sortez du shell du conteneur "web1" en tapant "exit"
# Attachez "web1" au mĂȘme rĂ©seau "mon-reseau"
docker network connect mon-reseau web1AprĂšs avoir suivi ces Ă©tapes, les conteneurs "web1" et "web2" devraient ĂȘtre attachĂ©s au mĂȘme rĂ©seau "mon-reseau" et ĂȘtre capables de communiquer entre eux.
# Lance un shell interactif dans le conteneur "web1"
docker container exec -it web1 bash# Ă l'intĂ©rieur du conteneur "web1," essayez de faire une requĂȘte HTTP vers "web2"
curl --noproxy '*' web2<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
[...]| Terme | Hors Docker (machine locale) | Dans Docker (environnement conteneurisé) |
|---|---|---|
| Votre ordinateur | Le conteneur actuel uniquement |
| Votre ordinateur | Le conteneur actuel uniquement |
| (n’existe pas) | Un autre conteneur du mĂȘme rĂ©seau |
| (n’existe pas) | Communication inter-conteneurs |
Dans Docker :
|
Ces erreurs proviennent d’examens rĂ©els (2025-2026).
Code cassé :
services:
backend:
image: mon-app
logging:
driver: gelf
options:
gelf-address: "udp://logstash:12201" # â FAUX !
tag: "backend"
logstash:
image: logstash:9.5.3
ports:
- "12201:12201/udp"ProblĂšme : le pilote gelf ne s’exĂ©cute pas dans le conteneur. Il s’exĂ©cute dans le dĂ©mon Docker de l’hĂŽte, qui n’est pas sur le rĂ©seau Compose et ne connaĂźt donc pas le nom logstash.
Le conteneur ne dĂ©marre mĂȘme pas :
Error response from daemon: failed to create task for container:
failed to initialize logging driver: gelf: cannot connect to GELF endpoint:
logstash:12201 dial udp: lookup logstash on ...: no such hostSolution :
services:
backend:
logging:
driver: gelf
options:
gelf-address: "udp://localhost:12201" # â
Adresse vue depuis l'hĂŽte
logstash:
image: logstash:9.5.3
ports:
- "12201:12201/udp" # â
le port publiĂ© rend logstash joignable depuis l'hĂŽteL’adresse d’un pilote de log se lit depuis l’hĂŽte, pas depuis le conteneur. C’est la seule exception de ce chapitre Ă la rĂšgle « autre conteneur â nom du service ». Les options |
Sans GELF utilise UDP : le dĂ©mon n’ouvre aucune connexion, il Ă©met des datagrammes. Si personne n’Ă©coute sur VĂ©rifiez toujours que les logs arrivent, pas seulement que le conteneur dĂ©marre. |
Restreindre la publication Ă une seule famille change tout. Avec C’est le mĂȘme piĂšge qu’au-dessus, avec une cause de plus : ici le port est publiĂ©, et l’Ă©criture En cas de doute, Ă©crire |
Code cassé :
services:
webapp:
image: node:18
healthcheck:
test: ["CMD", "curl", "-f", "http://[::1]:3000/health"] # â FAUX !ProblĂšme : [::1] (localhost IPv6) pointe vers le conteneur webapp lui-mĂȘme, mais si l’app Ă©coute sur IPv4…
Solution :
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"] # â
OK (mĂȘme conteneur)
# OU
test: ["CMD", "curl", "-f", "http://127.0.0.1:3000/health"] # â
OKHealth check = test dans le mĂȘme conteneur â C’est quand on veut accĂ©der Ă un autre service qu’il faut utiliser le nom du service. |
Code cassé (frontend) :
// frontend/src/config.js
const API_URL = "http://localhost:5000/api"; // â FAUX !ProblĂšme : Le navigateur du client exĂ©cute ce code â localhost pointe vers l’ordinateur du client, pas le backend Docker !
Solution 1 : Utiliser un reverse proxy (NGINX)
const API_URL = "/api"; // â
Relatif â passe par NGINXSolution 2 : Variable d’environnement (build-time)
const API_URL = process.env.REACT_APP_API_URL || "/api";services:
frontend:
build:
context: ./frontend
args:
# â
URL publique qui passe par le reverse proxy
REACT_APP_API_URL: "/api"React client-side â SSR/Next.js :
Recommandation : Toujours utiliser le reverse proxy (Solution 1) pour éviter la confusion ! |
| Situation | Utiliser | Exemple |
|---|---|---|
Health check (mĂȘme conteneur) |
| |
Communication entre services Docker | Nom du service | |
Logging GELF (options | Adresse vue depuis l’hĂŽte |
|
Adresse de connexion base de données | Nom du service |
|
API frontend â backend (mĂȘme rĂ©seau Docker) | Nom du service |
|
API frontend â backend (navigateur client) | Reverse proxy |
|
Localhost = Local Container
|
Corrigez ce docker-compose.yml cassé :
services:
webapp:
image: node:18
environment:
DATABASE_URL: "postgresql://localhost:5432/mydb" # â Erreur 1
REDIS_URL: "redis://127.0.0.1:6379" # â Erreur 2
logging:
driver: gelf
options:
gelf-address: "udp://localhost:12201" # â ïž PiĂšge : cette ligne est correcte
database:
image: postgres:15
cache:
image: redis:7
logstash:
image: logstash:9.5.3
ports:
- "12201:12201/udp"Solution : remplacer les localhost des variables d’environnement par les noms de services, et laisser l’adresse GELF telle quelle â le pilote de log est interprĂ©tĂ© par le dĂ©mon de l’hĂŽte (voir Erreur #1).
Erreurs fréquentes :
|
La derniĂšre ligne va Ă contre-courant des trois autres, et c’est voulu. Le bloc |
RĂšgle d’or :
|

Une application riche repose souvent sur plusieurs éléments techniques à coordonner ensemble.
(Apache, Tomcat, Node, MongoDB, ElasticSearch, Logstash, etc.)
Comment automatiser ces déploiements ?
#!/bin/sh
docker network create my-net
docker container run -d --net=my-net -p 3306:3306 mysql
docker container run -d --net=my-net -v /docs:/docs --name col1 httpd:2.4.58-bookworm
docker container run -d --net=my-net -v /docs:/docs --name col2 httpd:2.4.58-bookworm
Et tout lancer indépendamment ?
Et tout monitorer un par un ?
Peut mieux faire, non ?
services:services:
apache:
middle:
db:services:
apache:
image: "my-httpd:2.4"
middle:
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
middle:
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
middle:
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:
image: "mysql:5.6"services:
apache:
image: "my-httpd:2.4"
ports:
- "80:80"
environment:
- MIDDLE_HOST_1=middle
volumes:
- ./etc/httpd/workers.properties:/etc/httpd/conf/workers.properties
middle:
build: .
environment:
- DB_HOST=db
db:
image: "mysql:5.6"
ports:
- "3306:3306"docker compose up -d
docker compose build
docker compose logs
docker compose stop
docker compose restart
docker compose downL’ordre de dĂ©marrage fait rĂ©fĂ©rence Ă la sĂ©quence dans laquelle les services dĂ©finis dans un fichier Docker Compose sont lancĂ©s.
Pourquoi est-ce important ?
Certains services peuvent dĂ©pendre d’autres services pour fonctionner correctement.
Par exemple, une application web peut avoir besoin qu’une base de donnĂ©es soit opĂ©rationnelle avant de pouvoir dĂ©marrer.
Comment Docker Compose gĂšre-t-il l’ordre de dĂ©marrage ?
Par dĂ©faut, Docker Compose dĂ©marre les services dans l’ordre dans lequel ils sont dĂ©finis dans le fichier Docker Compose.
Cependant, cela ne garantit pas que les services dĂ©pendants seront prĂȘts Ă ĂȘtre utilisĂ©s lorsque les services qui en dĂ©pendent seront lancĂ©s.
Comment gérer les dépendances entre services ?
Docker Compose offre deux directives pour gérer les dépendances entre services : depends_on et healthcheck.
depends_on : Cette directive peut ĂȘtre utilisĂ©e pour indiquer qu’un service dĂ©pend d’un autre service.
Cependant, cela ne garantit pas que le service dĂ©pendant sera prĂȘt Ă ĂȘtre utilisĂ© lorsque le service qui en dĂ©pend sera lancĂ©.
healthcheck : Cette directive peut ĂȘtre utilisĂ©e pour vĂ©rifier l’Ă©tat de santĂ© d’un service.
En combinaison avec depends_on, elle peut aider Ă s’assurer qu’un service est prĂȘt Ă ĂȘtre utilisĂ© avant de lancer les services qui en dĂ©pendent.
Exemple
Voici un exemple de fichier Docker Compose qui utilise depends_on et healthcheck pour gĂ©rer l’ordre de dĂ©marrage des services :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 30s
retries: 3Dans cet exemple, le service web dĂ©pend du service db. Le service db est vĂ©rifiĂ© toutes les 30 secondes pour s’assurer qu’il est prĂȘt Ă ĂȘtre utilisĂ©. Le service web ne sera lancĂ© que lorsque le service db sera en bonne santĂ©.
Rappel:
La directive depends_on dans un fichier Docker Compose est utilisĂ©e pour indiquer qu’un service dĂ©pend d’un autre service.
Cela signifie que le service dĂ©pendant ne sera pas dĂ©marrĂ© tant que les services dont il dĂ©pend n’auront pas Ă©tĂ© dĂ©marrĂ©s. |
En plus de la condition service_healthy, il existe d’autres conditions que vous pouvez utiliser avec depends_on:
service_started : Cette condition signifie que le service dĂ©pendant ne sera pas dĂ©marrĂ© tant que le service dont il dĂ©pend n’aura pas Ă©tĂ© dĂ©marrĂ©.
service_completed_successfully: Cette condition indique qu’un service dĂ©pendant ne doit pas dĂ©marrer avant qu’un autre service ait terminĂ© avec succĂšs. Cela peut ĂȘtre utile dans des scĂ©narios oĂč un service doit effectuer une tĂąche unique qui doit ĂȘtre terminĂ©e avant que d’autres services puissent dĂ©buter.
Cependant, cela ne garantit pas que le service dont il dĂ©pend est prĂȘt Ă ĂȘtre utilisĂ©.
Voici un exemple de comment vous pourriez utiliser service_started dans un fichier Docker Compose :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_started
db:
image: postgresDans cet exemple, le service web ne sera démarré que lorsque le service db aura été démarré.
Rappel
healthcheck est une instruction dans un Dockerfile qui permet de vĂ©rifier l’Ă©tat de santĂ© d’un service.
Il peut ĂȘtre utilisĂ© pour dĂ©terminer si un service est prĂȘt Ă ĂȘtre utilisĂ© ou non. |
Voici un exemple de healthcheck dans un Dockerfile :
FROM postgres
HEALTHCHECK --interval=5m --timeout=3s \
CMD pg_isready -U postgres || exit 1Dans cet exemple, pg_isready -U postgres est la commande utilisĂ©e pour vĂ©rifier l’Ă©tat de santĂ© du service.
Si cette commande réussit, le service est considéré comme sain.
Sinon, il est considéré comme malsain.
Rappel
depends_on est une directive dans un fichier Docker Compose qui indique qu’un service dĂ©pend d’un autre service.
Il peut ĂȘtre utilisĂ© pour contrĂŽler l’ordre de dĂ©marrage des services. |
Voici un exemple de depends_on dans un fichier Docker Compose :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgresDans cet exemple, le service web dépend du service db.
Le service web ne sera démarré que lorsque le service db sera en bonne santé.
En combinant healthcheck et depends_on, vous pouvez contrĂŽler l’ordre de dĂ©marrage des services en fonction de leur Ă©tat de santĂ©.
Par exemple, vous pouvez vous assurer qu’un service de base de donnĂ©es est prĂȘt Ă ĂȘtre utilisĂ© avant de dĂ©marrer une application web qui en dĂ©pend.
L’ordre de dĂ©marrage fait rĂ©fĂ©rence Ă la sĂ©quence dans laquelle les services dĂ©finis dans un fichier Docker Compose sont lancĂ©s. Par dĂ©faut, Docker Compose dĂ©marre les services dans l’ordre dans lequel ils sont dĂ©finis dans le fichier Docker Compose.
Docker Compose offre deux directives pour gérer les dépendances entre services : depends_on et healthcheck.
depends_on : Cette directive peut ĂȘtre utilisĂ©e pour indiquer qu’un service dĂ©pend d’un autre service. Elle peut ĂȘtre utilisĂ©e avec deux conditions : service_started et service_healthy.
service_started : Cette condition signifie que le service dĂ©pendant ne sera pas dĂ©marrĂ© tant que le service dont il dĂ©pend n’aura pas Ă©tĂ© dĂ©marrĂ©. Cependant, cela ne garantit pas que le service dont il dĂ©pend est prĂȘt Ă ĂȘtre utilisĂ©.
service_healthy : Cette condition est utilisĂ©e avec la directive healthcheck dans un Dockerfile pour vĂ©rifier l’Ă©tat de santĂ© d’un service. Si la commande healthcheck rĂ©ussit, Docker considĂšre le service comme sain. Sinon, il est considĂ©rĂ© comme malsain.
Exemple Voici un exemple de fichier Docker Compose qui utilise depends_on et healthcheck pour gĂ©rer l’ordre de dĂ©marrage des services :
version: '3'
services:
web:
build: .
depends_on:
db:
condition: service_healthy
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 30s
timeout: 30s
retries: 3Un exemple de fichier docker-compose.yml utilisé dans Jenkins:
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5Les options stdin_open: true et tty: true sont utilisĂ©es pour garder l’entrĂ©e standard du conteneur ouverte et pour allouer un pseudo-TTY au conteneur, respectivement.
C’est gĂ©nĂ©ralement fait lorsque vous voulez interagir avec le service en cours d’exĂ©cution.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5La directive entrypoint est utilisée pour spécifier la commande qui sera exécutée lorsque le conteneur démarre.
Dans ce cas, elle exécute une commande shell qui exécute un script nommé keygen.sh situé à /usr/local/bin/keygen.sh.
Le script reçoit un argument /ssh-dir, qui est le rĂ©pertoire oĂč le script effectuera ses opĂ©rations.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5La directive volumes est utilisĂ©e pour monter un volume nommĂ© agent-ssh-dir sur le chemin /ssh-dir Ă l’intĂ©rieur du conteneur.
Cela permet de conserver les donnĂ©es entre les redĂ©marrages du conteneur et peut Ă©galement ĂȘtre utilisĂ© pour partager des donnĂ©es entre les conteneurs.
sidekick_service:
# Configuration for the sidekick service
image: ${DOCKERHUB_USERNAME}/jenkinsci-tutorials:sidekick_
stdin_open: true
tty: true
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # Runs the keygen.sh script and specifies the output directory
volumes:
- agent-ssh-dir:/ssh-dir # Mounts the agent-ssh-dir volume to the /ssh-dir path inside the container
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5Enfin, un healthcheck est dĂ©fini pour le service. Il s’agit d’une commande que Docker exĂ©cutera Ă l’intĂ©rieur du conteneur pour vĂ©rifier sa santĂ©.
Dans ce cas, la commande vérifie si un fichier nommé conductor_ok existe dans le répertoire /ssh-dir.
Si le fichier existe, la commande réussit et Docker considÚre le service comme sain.
Si le fichier n’existe pas, la commande Ă©choue et Docker considĂšre le service comme malsain.
Docker exécutera cette vérification de santé toutes les 5 secondes (interval: 5s), et si elle ne répond pas dans les 10 secondes (timeout: 10s), Docker la considérera comme un échec. Docker réessaiera une vérification de santé échouée 5 fois (retries: 5) avant de considérer le service comme malsain.
Si la commande utilisĂ©e dans un health check n’existe pas dans l’image, le health check Ă©chouera ! |
Les images slim et alpine sont minimalistes pour réduire la taille
Elles ne contiennent PAS les outils courants (curl, wget, bash, etc.)
Health check avec outil manquant â Status "unhealthy" â Conteneur inutilisable
| Image de Base | Outils Inclus | Outils ABSENTS |
|---|---|---|
| sh, bash, ps, grep | â wget, curl, netcat (installables via apt) |
| sh, bash, ps | â curl, wget, netcat |
| sh, wget, ps | â curl, bash |
| python, pip, bash, ps, wget, curl | (image complĂšte) |
| python, pip, sh, bash, ps | â curl, wget |
| python, pip, sh, wget, ps | â curl, bash |
| node, npm, bash, ps, wget, curl | (image complĂšte) |
| node, npm, sh, wget, ps | â curl, bash |
| pg_isready, psql, bash | (include outils PostgreSQL) |
| redis-cli, bash | (include outils Redis) |
| wget, bash, ps, curl | (version latest complÚte, nginx:alpine plus limité) |
Images officielles de bases de données (postgres, mysql, redis, mongodb) incluent généralement leurs propres outils de diagnostic. |
Quand un health check Ă©choue Ă cause d’un outil manquant :
docker compose psNAME IMAGE STATUS
myapp-backend python:3.11-slim Up 30 seconds (unhealthy)docker container inspect myapp-backend | grep -A 10 Health"Health": {
"Status": "unhealthy",
"FailingStreak": 5,
"Log": [
{
"ExitCode": 127, # â Code 127 = "Command not found"
"Output": "/bin/sh: curl: not found"
}
]
}ExitCode 127 = La commande n’existe pas dans l’image ! |
La mĂ©thode la plus fiable : installer l’outil manquant.
FROM python:3.11-slim
WORKDIR /app
# Installer curl pour le health check
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/* # â Nettoyer le cache apt
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]services:
backend:
build: ./backend
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"] # â
Fonctionne !
interval: 10s
timeout: 5s
retries: 3Alpine : Utiliser |
Utilisez les outils dĂ©jĂ prĂ©sents dans l’image.
| Besoin | Au lieu de… | Utiliser |
|---|---|---|
Tester connexion HTTP |
| |
Tester port ouvert |
|
|
Tester fichier existe |
|
|
Tester processus |
|
|
Exemple avec wget (disponible dans Alpine) :
services:
frontend:
image: node:18-alpine
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000 || exit 1"]
interval: 30s
timeout: 10s
retries: 3Cette solution s’applique uniquement aux images qui contiennent dĂ©jĂ Python ou Node.js (ex: |
Health check avec Python (pour images Python) :
services:
backend:
image: python:3.11-slim # â DĂ©jĂ contient Python
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"]
interval: 10s
timeout: 5s
retries: 3Health check avec Node.js (pour images Node) :
services:
api:
image: node:18-alpine # â DĂ©jĂ contient Node.js
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:8080/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
interval: 10s
timeout: 5s
retries: 3Corrigez ce docker-compose.yml qui échoue avec "unhealthy" :
services:
webapp:
image: python:3.11-slim
# Dockerfile n'installe PAS curl !
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 10s3 solutions possibles :
Ajouter RUN apt-get update && apt-get install -y curl dans Dockerfile
Remplacer par wget --spider --quiet http://localhost:5000/health
Remplacer par health check Python natif (Solution 3)
Avant d’Ă©crire un health check :
â VĂ©rifier : La commande existe-t-elle dans l’image ?
â
Tester : docker container run --rm image commande pour vérifier
â Choisir une solution :
Installer l’outil (Solution 1) â RecommandĂ© pour production
Utiliser alternative native (Solution 2) â LĂ©ger, pas d’install
Utiliser Python/Node built-in (Solution 3) â Images avec runtime uniquement
Erreurs fréquentes :
|
RĂšgle d’or : Si la commande Ă©choue â Elle Ă©chouera aussi dans le health check ! |
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-onlyService jenkins_controller
Ce service dépend du sidekick_service.
Il ne démarrera que lorsque le sidekick_service aura terminé avec succÚs.
C’est ce que signifie la condition service_completed_successfully.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-onlyUn healthcheck est également défini pour ce service.
Docker vérifie si un fichier nommé conductor_ok existe dans le chemin /ssh-dir.
Si le fichier existe, Docker considĂšre le service comme sain.
Sinon, il est considéré comme malsain.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-onlyService default_agent
Ce service dépend également du sidekick_service et du jenkins_controller.
Il ne démarrera que lorsque le sidekick_service aura terminé avec succÚs et que le jenkins_controller aura démarré.
C’est ce que signifient les conditions service_completed_successfully et service_started.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-onlyUn healthcheck est également défini pour ce service.
Docker vérifie si un fichier nommé authorized_keys existe dans le chemin /home/jenkins/.ssh.
Si le fichier existe, Docker considĂšre le service comme sain.
Sinon, il est considéré comme malsain.
services:
sidekick_service: [...]
jenkins_controller: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
healthcheck:
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Checks if the conductor_ok file exists in the /ssh-dir path
interval: 5s
timeout: 10s
retries: 5
default_agent: [...]
depends_on:
sidekick_service:
condition: service_completed_successfully # Depends on the successful completion of the sidekick_service
jenkins_controller:
condition: service_started
healthcheck:
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Checks if the authorized_keys file exists in the /home/jenkins/.ssh path
interval: 5s
timeout: 10s
retries: 5
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Mounts the agent-ssh-dir volume to the /home/jenkins/.ssh path inside the container as read-onlyEnfin, un volume nommĂ© agent-ssh-dir est montĂ© sur le chemin /home/jenkins/.ssh Ă l’intĂ©rieur du conteneur en lecture seule.
à étudier au calme chez vous:

Deux approches différentes et complémentaires :
Ăa vous dirait d’avoir votre propre forge Gitlab ?
đ Non, et bien c’est parti quand mĂȘme.
Créez un nouveau fichier appelé docker-compose.yml.
Dans ce fichier, définissez trois services : gitlab, gitlab-runner-1 et gitlab-runner-2.
Pour le service gitlab:
utilisez l’image gitlab/gitlab-ce:latest (prĂ©fixĂ©e par le cache spĂ©cifique ILI)
redĂ©marrez toujours le conteneur s’il s’arrĂȘte,
dĂ©finissez le nom d’hĂŽte sur localhost,
dĂ©finissez l’URL externe sur http://localhost et exposez les ports 80, 443 et 22.
Pour les services gitlab-runner-1 et gitlab-runner-2:
utilisez l’image gitlab/gitlab-runner:latest (prĂ©fixĂ©e par le cache spĂ©cifique ILI),
redĂ©marrez toujours le conteneur s’il s’arrĂȘte,
dépendez du service gitlab et montez le socket Docker et le fichier de configuration de GitLab Runner.
Créez des volumes pour:
les fichiers de configuration,
les fichiers journaux
et les fichiers de données de GitLab,
ainsi que pour les fichiers de configuration de GitLab Runner.
Pas assez dĂ©taillĂ©? đš
Ouvrez votre éditeur de texte préféré et créez un nouveau fichier.
Nommez ce fichier docker-compose.yml.
Commencez le fichier avec la ligne services: pour commencer à définir les services de votre application.
Commencez à définir votre premier service en tapant gitlab: sur une nouvelle ligne. Ce sera le service pour votre serveur GitLab.
Sous gitlab:, ajoutez les détails de votre service.
Par exemple, image: 'gitlab/gitlab-ce:latest' pour spĂ©cifier l’image Docker Ă utiliser pour ce service.
Sous gitlab:, ajoutez les détails de votre service.
Par exemple, image: 'gitlab/gitlab-ce:latest' pour spĂ©cifier l’image Docker Ă utiliser pour ce service.
Continuez Ă ajouter des dĂ©tails pour le service gitlab, comme restart: always pour toujours redĂ©marrer le conteneur s’il s’arrĂȘte, et hostname: 'localhost' pour dĂ©finir le nom d’hĂŽte du serveur GitLab.
DĂ©finissez l’URL externe de votre serveur GitLab en ajoutant environment: et GITLAB_OMNIBUS_CONFIG: | sur de nouvelles lignes,
puis external_url 'http://localhost' sur la ligne suivante.
Exposez les ports nécessaires en ajoutant ports: sur une nouvelle ligne,
puis - '80:80', - '443:443' et - '22:22' sur les lignes suivantes.
Montez les volumes nécessaires en ajoutant volumes: sur une nouvelle ligne,
puis - 'gitlab_config:/etc/gitlab',
- 'gitlab_logs:/var/log/gitlab'
et - 'gitlab_data:/var/opt/gitlab' sur les lignes suivantes.
Répétez ces étapes pour les services gitlab-runner-1 et gitlab-runner-2, en remplaçant gitlab par gitlab-runner-1 et gitlab-runner-2 respectivement.
Pour les services gitlab-runner-1 et gitlab-runner-2, remplacez l’URL externe par une dĂ©pendance au service gitlab en ajoutant depends_on: sur une nouvelle ligne, puis - gitlab sur la ligne suivante.
Montez le socket Docker et le fichier de configuration de GitLab Runner
en ajoutant - '/var/run/docker.sock:/var/run/docker.sock'
et - 'gitlab-runner-1-config:/etc/gitlab-runner' (ou - 'gitlab-runner-2-config:/etc/gitlab-runner' pour gitlab-runner-2) sous volumes:.
Enfin, définissez les volumes pour votre application en ajoutant volumes: sur une nouvelle ligne à la fin de votre fichier, puis
- gitlab_config:,
- gitlab_logs:,
- gitlab_data:,
- gitlab-runner-1-config:
et - gitlab-runner-2-config: sur les lignes suivantes.
# This is a Docker Compose file for setting up a GitLab server and two GitLab runners.
services:
# The GitLab server service
gitlab:
# The Docker image to use for the GitLab server
image: 'gitlab/gitlab-ce:16.6.0-ce.0'
# Always restart the container if it stops
restart: always
# The hostname for the GitLab server
hostname: 'localhost'
# Environment variables for the GitLab server
environment:
# Configuration for the GitLab Omnibus package
GITLAB_OMNIBUS_CONFIG: |
# The external URL for the GitLab server
external_url 'http://localhost'
# The ports to expose from the GitLab server
ports:
- '80:80' # HTTP
- '443:443' # HTTPS
- '22:22' # SSH
# The volumes to mount for the GitLab server
volumes:
- 'gitlab_config:/etc/gitlab' # Configuration files
- 'gitlab_logs:/var/log/gitlab' # Log files
- 'gitlab_data:/var/opt/gitlab' # Data files
# The first GitLab runner service
gitlab-runner-1:
# The Docker image to use for the GitLab runner
image: 'gitlab/gitlab-runner:alpine3.16-v16.6.0'
# Always restart the container if it stops
restart: always
# The services this service depends on
depends_on:
- gitlab # Depends on the GitLab server
# The volumes to mount for the GitLab runner
volumes:
- '/var/run/docker.sock:/var/run/docker.sock' # Docker socket for running Docker commands
- 'gitlab-runner-1-config:/etc/gitlab-runner' # Configuration files
# The second GitLab runner service
gitlab-runner-2:
# The Docker image to use for the GitLab runner
image: 'gitlab/gitlab-runner:alpine3.16-v16.6.0'
# Always restart the container if it stops
restart: always
# The services this service depends on
depends_on:
- gitlab # Depends on the GitLab server
# The volumes to mount for the GitLab runner
volumes:
- '/var/run/docker.sock:/var/run/docker.sock' # Docker socket for running Docker commands
- 'gitlab-runner-2-config:/etc/gitlab-runner' # Configuration files
# The volumes to create for the services
volumes:
gitlab_config: # Volume for GitLab server configuration files
gitlab_logs: # Volume for GitLab server log files
gitlab_data: # Volume for GitLab server data files
gitlab-runner-1-config: # Volume for GitLab runner 1 configuration files
name: gitlab-runner-1-config
gitlab-runner-2-config: # Volume for GitLab runner 2 configuration files
name: gitlab-runner-2-config
Il va falloir se logguer, lier les runners au serveur, crĂ©er un projet, etc…

Pour trouver le mot de passe, il va falloir le demander gentiment Ă Gitlab:
docker compose -f tp12-bis.yml exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password
Password: qaPxXU+RqioolV3bAljvs2VYnyYa4jO/UYcis/UXLAk=Ensuite, il va falloir restreindre l’accĂšs:

Pour utiliser le runner GitLab dans GitLab, vous devez le configurer.
Pour une configuration correcte, nous aurons besoin d’un jeton copiĂ© depuis le portail.
Pire que ça, pas un jeton, mais carrément une commande à adapter à docker compose.
Pour ce faire, allez Ă l’adresse : http://localhost/admin/runners et cliquez sur le bouton "New instance runner".
Choisissez "Linux".
"Run untagged jobs"
Donnez une description au runner.
Cliquez sur le bouton "Create runner"
Vous avez ensuite une commande Ă copier et modifier pour docker compose.
docker compose -f tp12-bis.yml exec gitlab-runner-1 gitlab-runner register --url http://gitlab --token glrt-PF5rLbUKzku5g8BL2y7J
Runtime platform arch=amd64 os=linux pid=103 revision=853330f9 version=16.5.0
Running in system-mode.
Enter the GitLab instance URL (for example, https://gitlab.com/):
[http://gitlab]:
Verifying runner... is valid runner=PF5rLbUKz
Enter a name for the runner. This is stored only in the local config.toml file:
[3c2ee0d97d2b]: runner 1
Enter an executor: parallels, docker-autoscaler, docker+machine, custom, docker, docker-windows, shell, ssh, virtualbox, instance, kubernetes:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
Configuration (with the authentication token) was saved in "/etc/gitlab-runner/config.toml"On a vu dĂ©jĂ l’instruction depends_on dans un fichier Docker Compose.
Elle permet de définir des dépendances entre les services.
Mais que faire si on veut dĂ©finir un lot de services qui fonctionnent ensemble, sans pour autant ĂȘtre en interdĂ©pendanceâŻ?
Les profils dans Docker Compose permettent de dĂ©finir des groupes de services qui peuvent ĂȘtre activĂ©s ou dĂ©sactivĂ©s ensemble.
Cela peut ĂȘtre utile pour gĂ©rer des environnements de dĂ©veloppement, de test et de production diffĂ©rents dans le mĂȘme fichier Docker Compose.
Pour définir un profil pour un service, vous pouvez ajouter la clé profiles à la définition du service dans votre fichier Docker Compose.
Par exemple :
services:
mon_service:
image: mon_image
profiles:
- devDans cet exemple, le service mon_service appartient au profil dev.
Pour dĂ©marrer seulement les services qui appartiennent Ă un certain profil, vous pouvez utiliser l’option --profile de la commande docker compose, placĂ©e avant la sous-commande up.
Par exemple :
docker compose --profile dev upCette commande démarrera seulement les services qui appartiennent au profil dev.
Les profils peuvent rendre votre fichier Docker Compose plus organisé et flexible.
Ils vous permettent de dĂ©finir diffĂ©rents environnements dans le mĂȘme fichier et de choisir facilement quels services dĂ©marrer en fonction de vos besoins.
Jetons un coup d’Ćil Ă un exemple de fichier Docker Compose avec des profils :
services:
service_dev:
image: mon_image_dev
profiles:
- dev
service_prod:
image: mon_image_prod
profiles:
- prodDans cet exemple, nous avons deux services : service_dev et service_prod.
Chacun appartient à un profil différent.
services:
service_dev:
image: mon_image_dev
profiles:
- dev
service_prod:
image: mon_image_prod
profiles:
- prodNous pouvons choisir de démarrer seulement les services de développement avec docker compose --profile dev up, ou uniquement les services de production avec docker compose --profile prod up.
Les profils dans Docker Compose sont un outil puissant pour gĂ©rer diffĂ©rents environnements dans le mĂȘme fichier Docker Compose.
Ils peuvent rendre votre développement et vos tests plus efficaces et organisés.
đ Non, et bien, c’est parti quand mĂȘme.
services:
# Le service sidekick est responsable de la génération des clés SSH et de la vérification de leur existence.
sidekick_service:
build: dockerfiles/sidekick/. # Le Dockerfile pour construire l'image du service sidekick.
stdin_open: true # Permet au service de garder STDIN ouvert mĂȘme s'il n'est pas attachĂ©.
tty: true # Alloue un pseudo-TTY pour le service.
entrypoint: sh -c "/usr/local/bin/keygen.sh /ssh-dir" # La commande que le service exécute lorsqu'il démarre.
volumes:
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service jenkins_controller est responsable de la gestion des jobs et des configurations Jenkins.
jenkins_controller:
build: dockerfiles/. # Le Dockerfile pour construire l'image du service jenkins_controller.
restart: on-failure # Le service sera redémarré s'il quitte en raison d'une erreur.
ports:
- "8080:8080" # Expose le port 8080 du service Ă l'hĂŽte.
volumes:
- jenkins_home:/var/jenkins_home # Monte le volume jenkins_home au chemin /var/jenkins_home à l'intérieur du conteneur.
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service default_agent est un agent Jenkins avec un JDK installé.
default_agent:
image: jenkins/ssh-agent:5.5.0-jdk17 # L'image Docker pour le service.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service maven est un agent Jenkins avec Maven installé.
maven:
build: dockerfiles/maven/. # Le Dockerfile pour construire l'image du service Maven.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- maven # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service python est un agent Jenkins avec Python installé.
python:
build: dockerfiles/python/. # Le Dockerfile pour construire l'image du service Python.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- python # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service node est un agent Jenkins avec Node.js installé.
node:
build: dockerfiles/node/. # Le Dockerfile pour construire l'image du service Node.js.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- node # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
ports:
- "3000:3000" # Expose le port 3000 du service Ă l'hĂŽte.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Le service multi_jenkins_controller est un contrÎleur Jenkins pour gérer plusieurs jobs et configurations Jenkins.
multi_jenkins_controller:
build: 06_multibranch_pipeline/dockerfiles/. # Le Dockerfile pour construire l'image du service multi Jenkins controller.
restart: on-failure # Le service sera redémarré s'il quitte en raison d'une erreur.
ports:
- "8080:8080" # Expose le port 8080 du service Ă l'hĂŽte.
volumes:
- jenkins_home:/var/jenkins_home # Monte le volume jenkins_home au chemin /var/jenkins_home à l'intérieur du conteneur.
- agent-ssh-dir:/ssh-dir # Monte le volume agent-ssh-dir au chemin /ssh-dir à l'intérieur du conteneur.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /ssh-dir/conductor_ok ] || exit 1" ] # Vérifie si le fichier conductor_ok existe dans le chemin /ssh-dir.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
# Le service multi est un agent Jenkins pour gérer plusieurs jobs et configurations Jenkins.
multi:
build: 06_multibranch_pipeline/dockerfiles/agent/. # Le Dockerfile pour construire l'image du service multi Jenkins agent.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- multi # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
multi_jenkins_controller:
condition: service_started # Le service multi_jenkins_controller doit démarrer avant que ce service ne démarre.
ports:
- "3000:3000" # Expose le port 3000 du service Ă l'hĂŽte.
- "5000:5000" # Expose le port 5000 du service Ă l'hĂŽte.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.
# Les volumes utilisés par les services.
volumes:
jenkins_home: # Un volume pour stocker les données de Jenkins.
agent-ssh-dir: # Un volume pour stocker les clés SSH.
name: agent-ssh-dir # Le nom du volume.Indigeste? đš
Ne relevons que les points importants:
# Le service maven est un agent Jenkins avec Maven installé.
maven:
build: dockerfiles/maven/. # Le Dockerfile pour construire l'image du service Maven.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- maven # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.Maven
FROM jenkins/ssh-agent:9.0.0-jdk21 as ssh-agent
# ca-certificates because curl will need it later on for the maven installation
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl && apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Now time to install maven
ARG MAVEN_VERSION=3.9.2
# Set SHELL flags for RUN commands to allow -e and pipefail
# Rationale:https://github.com/hadolint/hadolint/wiki/DL4006
SHELL ["/bin/bash", "-eo", "pipefail", "-c"]
# Add a checksum for the maven binary
RUN curl -sS -L -O --output-dir /tmp/ --create-dirs https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
&& printf "%s" "$(sha512sum /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz)" | sha512sum -c - \
&& curl -sS -L -O --output-dir /tmp/ --create-dirs https://downloads.apache.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512 \
&& printf "%s /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" "$(cat /tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz.sha512)" | sha512sum --check --status - \
&& tar xzf "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -C /opt/ \
&& rm "/tmp/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
&& ln -s /opt/apache-maven-${MAVEN_VERSION} /opt/maven \
&& ln -s /opt/maven/bin/mvn /usr/bin/mvn \
&& mkdir -p /etc/profile.d \
&& echo "export JAVA_HOME=$JAVA_HOME \n \
export M2_HOME=/opt/maven \n \
export PATH=${M2_HOME}/bin:${PATH}" > /etc/profile.d/maven.sh
ENV M2_HOME="/opt/maven"
ENV PATH="${M2_HOME}/bin/:${PATH}"
RUN echo "PATH=${PATH}" >> /etc/environment && chown -R jenkins:jenkins "${JENKINS_AGENT_HOME}"Python
# Le service python est un agent Jenkins avec Python installé.
python:
build: dockerfiles/python/. # Le Dockerfile pour construire l'image du service Python.
container_name: desktop-jenkins_agent-1 # Le nom du conteneur.
profiles:
- python # Les profils à appliquer au service. Cela permet de personnaliser le comportement du service en fonction des besoins spécifiques.
depends_on: # Les services dont ce service dépend.
sidekick_service:
condition: service_completed_successfully # Le sidekick_service doit se terminer avec succÚs avant que ce service ne démarre.
jenkins_controller:
condition: service_started # Le service jenkins_controller doit démarrer avant que ce service ne démarre.
healthcheck: # La commande de vérification de santé pour le service.
test: [ "CMD-SHELL", "[ -f /home/jenkins/.ssh/authorized_keys ] || exit 1" ] # Vérifie si le fichier authorized_keys existe dans le chemin /home/jenkins/.ssh.
interval: 5s # Le temps entre les vérifications de santé.
timeout: 10s # Le temps à attendre avant de considérer que la vérification a échoué.
retries: 5 # Le nombre d'échecs consécutifs nécessaires pour considérer un service comme malsain.
volumes:
- agent-ssh-dir:/home/jenkins/.ssh:ro # Monte le volume agent-ssh-dir au chemin /home/jenkins/.ssh à l'intérieur du conteneur en lecture seule.# Use the base image
FROM jenkins/ssh-agent:8.6.0-jdk17 as ssh-agent
# Install necessary Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
binutils ca-certificates curl git python3 python3-pip python3-setuptools python3-wheel python3-dev wget
# Create an alias for python3 as python
RUN ln -s /usr/bin/python3 /usr/bin/python
# Install required python packages
RUN pip install docker-py feedparser nosexcover prometheus_client pycobertura pylint pytest pytest-cov requests setuptools sphinx pyinstaller
# Add the Jenkins agent user to the environment
RUN echo "PATH=${PATH}" >> /etc/environment
# Set ownership for Jenkins agent home directory
RUN chown -R jenkins:jenkins "${JENKINS_AGENT_HOME}"Ă vous maintenant!
git clone https://github.com/jenkins-docs/quickstart-tutorials.gità vous de modifier les sources de façon à passer par le cache ILI
Ensuite, ne restera qu’Ă lancer la "forge":
docker compose up --build -d --force-recreate mavenOu encore si vous voulez tout construire localement:
docker compose -f build-docker-compose.yaml up --build -d --force-recreate mavenEt pourquoi pas s’attaquer au docker compose qui a gĂ©nĂ©rĂ© ce coursâŻ?
Et pourquoi pas s’attaquer au docker compose qui a gĂ©nĂ©rĂ© ce coursâŻ?
# Ceci est un fichier Docker Compose pour un projet qui comprend plusieurs services.
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des E/S plus rapides ; 1777 car le conteneur n'est pas root.
tmpfs:
- ${BUILD_DIR}:mode=1777
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packages
# Les services qui composent l'application.
services:
# Le service 'serve'.
serve:
# Utilise la configuration commune définie par l'ancre 'x-slides-base'.
<<: *slides-base
# Expose le port 8000 du conteneur Ă l'hĂŽte.
ports:
- "8000:8000"
# Le service 'build'.
build:
<<: *slides-base
# Ce service dépend du service 'qrcode'.
depends_on:
qrcode:
# Le service 'qrcode' doit se terminer avec succÚs avant que ce service ne démarre.
condition: service_completed_successfully
# Le point d'entrée pour le conteneur Docker.
entrypoint: >
sh -xc 'gulp build && cp -r "${BUILD_DIR}"/* /app/dist/'
# Le service 'qrcode'.
qrcode:
<<: *slides-base
# Le point d'entrée pour le conteneur Docker.
entrypoint: /app/node_modules/.bin/qrcode
# La commande à exécuter dans le conteneur Docker.
command: >
-t png -o /app/content/media/qrcode.png ${PRESENTATION_URL}
# Le service 'pdf'.
pdf:
# L'image est construite localement, Ă partir de decktape : voir dockerfiles/pdf/.
build:
context: ./dockerfiles/pdf
# Ce service dépend du service 'build'.
depends_on:
build:
# Le service 'build' doit se terminer avec succÚs avant que ce service ne démarre.
condition: service_completed_successfully
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
environment:
# Chromium veut un HOME inscriptible pour sa base crashpad. L'image n'en
# déclare un que pour l'uid 1000 : tout autre uid tombe sur HOME=/ et le
# navigateur refuse de démarrer.
- HOME=/tmp
# Volumes pour le conteneur Docker.
volumes:
- ${DIST_DIR}:/slidesEt pourquoi pas s’attaquer au docker compose qui a gĂ©nĂ©rĂ© ce coursâŻ?
Indigeste? đš
Ne relevons que les points importants:
Les ancres…
# Ceci est un fichier Docker Compose pour un projet qui comprend plusieurs services.
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des E/S plus rapides ; 1777 car le conteneur n'est pas root.
tmpfs:
- ${BUILD_DIR}:mode=1777
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packagesdocker compose âDocker Compose permet de dĂ©finir et de gĂ©rer plusieurs services Docker dans un seul fichier YAML.
Les ancres (&) et les alias (*) sont des fonctionnalitĂ©s YAML qui peuvent ĂȘtre utilisĂ©es dans Docker Compose pour rĂ©utiliser des configurations.
Une ancre est une référence à un objet ou à une valeur dans un fichier YAML.
Elle est dĂ©finie en utilisant l’opĂ©rateur & suivi d’un nom unique.
x-slides-base: &slides-base
build:
context: ./Dans cet exemple, x-slides-base est une ancre qui représente un objet avec une clé build.
docker compose âLes ancres peuvent ĂȘtre rĂ©fĂ©rencĂ©es ailleurs dans le fichier YAML en utilisant l’opĂ©rateur *.
services:
serve:
<<: *slides-baseLes ancres permettent de réutiliser des configurations, ce qui rend le fichier Docker Compose plus lisible et plus facile à maintenir.
Si vous devez modifier une configuration qui est utilisĂ©e Ă plusieurs endroits, vous pouvez simplement modifier l’ancre.
En conclusion, les ancres et les alias sont des outils puissants pour gĂ©rer des configurations complexes dans Docker Compose. Ils permettent de rĂ©duire la duplication et d’amĂ©liorer la lisibilitĂ© de votre fichier Docker Compose.
Le fameux qrcode…
# Le service 'qrcode'.
qrcode:
<<: *slides-base
# Le point d'entrée pour le conteneur Docker.
entrypoint: /app/node_modules/.bin/qrcode
# La commande à exécuter dans le conteneur Docker.
command: >
-t png -o /app/content/media/qrcode.png ${PRESENTATION_URL}Le service qui construit les slides Ă partir de asciidoc avec reveal.js.
# Le service 'build'.
build:
<<: *slides-base
# Ce service dépend du service 'qrcode'.
depends_on:
qrcode:
# Le service 'qrcode' doit se terminer avec succÚs avant que ce service ne démarre.
condition: service_completed_successfully
# Le point d'entrée pour le conteneur Docker.
entrypoint: >
sh -xc 'gulp build && cp -r "${BUILD_DIR}"/* /app/dist/'Le service qui construit les slides Ă partir de asciidoc avec reveal.js.
# Le service 'serve'.
serve:
# Utilise la configuration commune définie par l'ancre 'x-slides-base'.
<<: *slides-base
# Expose le port 8000 du conteneur Ă l'hĂŽte.
ports:
- "8000:8000"Le service qui construit le pdf Ă partir des slides.
# Le service 'pdf'.
pdf:
# L'image est construite localement, Ă partir de decktape : voir dockerfiles/pdf/.
build:
context: ./dockerfiles/pdf
# Ce service dépend du service 'build'.
depends_on:
build:
# Le service 'build' doit se terminer avec succÚs avant que ce service ne démarre.
condition: service_completed_successfully
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
environment:
# Chromium veut un HOME inscriptible pour sa base crashpad. L'image n'en
# déclare un que pour l'uid 1000 : tout autre uid tombe sur HOME=/ et le
# navigateur refuse de démarrer.
- HOME=/tmp
# Volumes pour le conteneur Docker.
volumes:
- ${DIST_DIR}:/slidesL’image publiĂ©e ghcr.io/astefanutti/decktape ne suffit plus telle quelle.
Le PDF passe par deux Ă©tapes : decktape rend les diapositives en 2048x1536, puis ghostscript rĂ©duit le fichier. L’image publiĂ©e n’a pas ghostscript.
Chromium ne dĂ©marre dans le conteneur qu’avec --no-sandbox et --disable-gpu. Ces options vivent dans entrypoint.sh, d’oĂč l’absence de command: dans le service.
L’image ne dĂ©clare un HOME que pour l’uid 1000. Avec un autre uid, Chromium Ă©choue sur chrome_crashpad_handler: --database is required : d’oĂč HOME=/tmp.
Un petit Dockerfile FROM ghcr.io/astefanutti/decktape rĂšgle les deux premiers points ; Compose le construit avec build:.
Pour rappel, l’ancre contenait :
# 'x-slides-base' est une ancre YAML qui définit une configuration commune pour plusieurs services.
x-slides-base: &slides-base
# La configuration de construction pour l'image Docker.
build:
# Le contexte de construction est le répertoire courant.
context: ./
args:
# Active la fonction de mise en cache en ligne de Docker BuildKit.
BUILDKIT_INLINE_CACHE: 1
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}
# Un montage tmpfs pour des E/S plus rapides ; 1777 car le conteneur n'est pas root.
tmpfs:
- ${BUILD_DIR}:mode=1777
# Volumes pour le conteneur Docker.
volumes:
- ./content:/app/content
- ./assets:/app/assets
- ${DIST_DIR}:/app/dist
- ./gulp/gulpfile.js:/app/gulpfile.js
- ./gulp/tasks:/app/tasks
- ./npm-packages:/app/npm-packagesDocker BuildKit est un outil de construction de Docker qui apporte de nombreuses amĂ©liorations par rapport Ă l’ancien systĂšme de construction.
L’une de ces amĂ©liorations est la mise en cache en ligne.
La mise en cache en ligne est une fonctionnalitĂ© de Docker BuildKit qui permet de rĂ©utiliser les couches de cache existantes lors de la construction d’une image Docker.

Elle est activĂ©e en dĂ©finissant l’argument BUILDKIT_INLINE_CACHE Ă 1.
args:
BUILDKIT_INLINE_CACHE: 1 # Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}Trois variables d’environnement sont dĂ©finies pour le conteneur Docker : PRESENTATION_URL, REPOSITORY_URL et SOURCE_FILE.
SOURCE_FILE choisit le fichier racine : SOURCE_FILE=index-examen.adoc make build construit le sujet d’examen.
Ces variables sont dĂ©finies Ă l’aide de la syntaxe ${VARIABLE_NAME}, qui est une maniĂšre standard d’accĂ©der aux variables d’environnement dans les fichiers de configuration YAML.
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}Lorsque Docker Compose rencontre cette syntaxe, il cherche la valeur de la variable d’environnement dans plusieurs endroits, en suivant un ordre spĂ©cifique :
Il vĂ©rifie d’abord si la variable est dĂ©finie dans le shell courant.
Si c’est le cas, il utilise cette valeur.
Si la variable n’est pas dĂ©finie dans le shell, Docker Compose cherche ensuite dans un fichier .env situĂ© dans le mĂȘme rĂ©pertoire que le fichier docker-compose.yml.
Si la variable est définie dans ce fichier, Docker Compose utilise cette valeur.
# Variables d'environnement pour le conteneur Docker.
environment:
- PRESENTATION_URL=${PRESENTATION_URL}
- REPOSITORY_URL=${REPOSITORY_URL}
# Le fichier racine Ă construire : SOURCE_FILE=index-examen.adoc make build
- SOURCE_FILE=${SOURCE_FILE}Si la variable n’est dĂ©finie ni dans le shell ni dans le fichier .env, Docker Compose utilise la valeur par dĂ©faut spĂ©cifiĂ©e dans le fichier docker-compose.yml (s’il y en a une).
Dans notre cas, aucune valeur par dĂ©faut n’est spĂ©cifiĂ©e. Docker Compose n’Ă©choue pas pour autant : il Ă©met un avertissement, substitue une chaĂźne vide, et continue.
The "PRESENTATION_URL" variable is not set. Defaulting to a blank string. â code de sortie 0.
# L'ID utilisateur qui exécute les commandes à l'intérieur du conteneur.
user: ${CURRENT_UID}Dans Docker, chaque instruction dans le Dockerfile est exécutée par un utilisateur particulier.
Par dĂ©faut, cet utilisateur est root, mais pour des raisons de sĂ©curitĂ©, il est souvent recommandĂ© d’exĂ©cuter les processus en tant qu’utilisateur non root.
Cette ligne dĂ©finit l’ID de l’utilisateur qui exĂ©cutera les commandes Ă l’intĂ©rieur du conteneur Docker Ă la valeur de la variable d’environnement CURRENT_UID.
La syntaxe ${CURRENT_UID} est utilisĂ©e pour accĂ©der Ă la valeur d’une variable d’environnement.
Dans ce cas, Docker Compose recherchera une variable d’environnement nommĂ©e CURRENT_UID et utilisera sa valeur.
Si CURRENT_UID n’est pas dĂ©fini, Docker Compose n’Ă©choue pas : mĂȘme avertissement, mĂȘme chaĂźne vide. user: reçoit une valeur vide.
Le conteneur dĂ©marre donc en root â exactement l’inverse de ce que cette ligne cherchait Ă obtenir.
Permissif :
user: ${CURRENT_UID}Obligatoire :
user: ${CURRENT_UID:?CURRENT_UID est obligatoire}${VAR} est permissif : variable absente, valeur vide, on continue.
${VAR:?message} Ă©choue pour de bon, dĂšs docker compose config : le message s’affiche et le code de sortie est 1.
Rien ne démarre, donc rien ne démarre en root.
C’est la forme Ă Ă©crire quand la valeur est obligatoire.
C’est celle qu’utilise le docker-compose.yml Ă la racine de ce dĂ©pĂŽt : sans CURRENT_UID (que make fournit), il s’arrĂȘte au lieu de construire en root.
# Ce Dockerfile met en place un environnement Node.js avec des outils et dépendances supplémentaires.
# Il est basé sur l'image Docker officielle de Node.js (variante Alpine).
FROM node:26-alpine
# Installer la derniÚre version des dépendances requises
# hadolint ignore=DL3018
RUN apk add --no-cache \
curl \ # Outil pour transférer des données avec des URLs
git \ # SystÚme de contrÎle de version distribué
tini \ # Un init minuscule mais valide pour les conteneurs
unzip # Outil pour décompresser les fichiers zip
# Installer les dépendances NPM globalement (derniÚres versions)
# hadolint ignore=DL3016,DL3059
RUN npm install --global npm npm-check-updates # Mettre Ă jour npm Ă la derniĂšre version et installer npm-check-updates
# Copier les dépendances NPM de l'application dans l'image Docker
COPY ./npm-packages /app/npm-packages
# Créer des liens symboliques pour package.json et package-lock.json à la racine de /app
# Cela permet d'exécuter les opérations npm sans erreur ENOENT
RUN ln -s /app/npm-packages/package.json /app/package.json \
&& ln -s /app/npm-packages/package-lock.json /app/package-lock.json
# Définir le répertoire de travail dans l'image Docker à /app
WORKDIR /app
# Télécharger et installer une version spécifique de FontAwesome
ARG FONTAWESOME_VERSION=6.4.0
RUN curl --silent --show-error --location --output /tmp/fontawesome.zip \
"https://use.fontawesome.com/releases/v${FONTAWESOME_VERSION}/fontawesome-free-${FONTAWESOME_VERSION}-web.zip" \
&& unzip -q /tmp/fontawesome.zip -d /tmp \
&& mv /tmp/"fontawesome-free-${FONTAWESOME_VERSION}-web" /app/fontawesome \
&& rm -rf /tmp/font*
# Installer les dépendances NPM en utilisant le package-lock.json
# Si l'installation échoue, revenir à une installation npm réguliÚre
RUN { npm install-clean && npx update-browserslist-db@latest; } || npm install
# Lier la commande gulp pour qu'elle soit disponible dans le PATH
# hadolint ignore=DL3059
RUN npm link gulp
# Copier les tĂąches gulp et la configuration dans l'image Docker
COPY ./gulp/tasks /app/tasks
COPY ./gulp/gulpfile.js /app/gulpfile.js
# Définir un volume pour le répertoire /app
VOLUME ["/app"]
# Exposer le port 8000 pour HTTP
EXPOSE 8000
# Utiliser tini comme point d'entrée, et exécuter gulp par défaut
ENTRYPOINT ["/sbin/tini","-g","gulp"]
CMD ["default"]RUN npm install --global npm npm-check-updates # Mettre Ă jour npm Ă la derniĂšre version et installer npm-check-updates
# Copier les dépendances NPM de l'application dans l'image Docker
COPY ./npm-packages /app/npm-packages
# Créer des liens symboliques pour package.json et package-lock.json à la racine de /app
# Cela permet d'exécuter les opérations npm sans erreur ENOENT
RUN ln -s /app/npm-packages/package.json /app/package.json \
&& ln -s /app/npm-packages/package-lock.json /app/package-lock.jsonCe Dockerfile met en place un environnement Node.js avec des outils et dépendances supplémentaires.
Il installe les derniĂšres versions de npm et npm-check-updates globalement, copie les dĂ©pendances npm de l’application dans l’image Docker, et crĂ©e des liens symboliques pour package.json et package-lock.json Ă la racine de /app.
ARG FONTAWESOME_VERSION=6.4.0
RUN curl --silent --show-error --location --output /tmp/fontawesome.zip \
"https://use.fontawesome.com/releases/v${FONTAWESOME_VERSION}/fontawesome-free-${FONTAWESOME_VERSION}-web.zip" \
&& unzip -q /tmp/fontawesome.zip -d /tmp \
&& mv /tmp/"fontawesome-free-${FONTAWESOME_VERSION}-web" /app/fontawesome \
&& rm -rf /tmp/font*Il télécharge également et installe une version spécifique de FontAwesome
# Installer les dépendances NPM en utilisant le package-lock.json
# Si l'installation échoue, revenir à une installation npm réguliÚre
RUN { npm install-clean && npx update-browserslist-db@latest; } || npm install
# Lier la commande gulp pour qu'elle soit disponible dans le PATH
# hadolint ignore=DL3059
RUN npm link gulpIl installe les dĂ©pendances npm en utilisant le package-lock.json (en revenant Ă une installation npm rĂ©guliĂšre si nĂ©cessaire), et lie la commande gulp pour qu’elle soit disponible dans le PATH.
# Copier les tĂąches gulp et la configuration dans l'image Docker
COPY ./gulp/tasks /app/tasks
COPY ./gulp/gulpfile.js /app/gulpfile.js
# Définir un volume pour le répertoire /app
VOLUME ["/app"]
# Exposer le port 8000 pour HTTP
EXPOSE 8000Les tĂąches gulp et la configuration sont copiĂ©es dans l’image Docker, un volume est dĂ©fini pour le rĂ©pertoire /app, et le port 8000 est exposĂ© pour HTTP.
# Utiliser tini comme point d'entrée, et exécuter gulp par défaut
ENTRYPOINT ["/sbin/tini","-g","gulp"]
CMD ["default"]Le point d’entrĂ©e est dĂ©fini sur tini, et gulp est exĂ©cutĂ© par dĂ©faut.
Tini est un init minuscule mais valide pour les conteneurs. Il est conçu pour ĂȘtre le systĂšme init le plus simple possible.
Tini fait deux choses :
Il génÚre votre processus en tant que son enfant (directement, pas en tant que petit-enfant, comme le ferait un shell).
Il attend ensuite les signaux et les transmet au processus enfant.
Docker exécute un seul processus dans un conteneur par défaut. Si ce processus génÚre des processus enfants et ne les récolte pas correctement, ils deviennent des processus zombies.
Tini assure que ces processus zombies sont correctement récoltés, améliorant ainsi le comportement du conteneur et réduisant la probabilité de cas limites.
Dans notre Dockerfile, nous utilisons Tini comme point d’entrĂ©e :
ENTRYPOINT ["/sbin/tini","-g","gulp"]Cela signifie que Tini est le premier processus qui est lancé dans notre conteneur.
Il lancera ensuite gulp en tant que processus enfant.
Tout signal envoyé au conteneur sera transmis par Tini à gulp.
Si gulp génÚre des processus enfants et ne les récolte pas, Tini le fera.
# Ce Makefile est utilisé pour gérer le projet Docker Compose.
# Définir les valeurs par défaut pour les variables DIST_DIR et REPOSITORY_URL.
DIST_DIR ?= $(CURDIR)/dist
REPOSITORY_URL ?= file://$(CURDIR)
export REPOSITORY_URL DIST_DIR
# Les conteneurs de construction écrivent dans dist/ à travers un montage bind :
# ils doivent donc tourner sous l'utilisateur qui lance make. Laissés en root, ils
# y déposent des fichiers que `clean` ne peut plus supprimer depuis l'hÎte, et
# `make all` meurt sur sa premiÚre cible. Calculé ici plutÎt qu'écrit en dur : cet
# identifiant n'est pas le mĂȘme sur un poste, dans un Codespace ou sur un agent
# d'intégration continue.
CURRENT_UID ?= $(shell id -u):$(shell id -g)
export CURRENT_UID
# Activer Docker BuildKit pour une construction plus rapide et la mise en cache des images.
DOCKER_BUILDKIT ?= 1
COMPOSE_DOCKER_CLI_BUILD ?= 1
export DOCKER_BUILDKIT COMPOSE_DOCKER_CLI_BUILD
# Définition des commandes shell réutilisables pour Docker Compose.
# compose_cmd est une fonction qui exécute la commande 'docker compose' avec le fichier docker-compose.yml du répertoire courant.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose'.
# $(CURDIR) est une variable d'environnement dans le Makefile qui représente le répertoire courant dans lequel
# le Makefile est exécuté. C'est une fonctionnalité intégrée de GNU Make. Elle est souvent utilisée pour référencer
# des fichiers ou des répertoires relatifs au répertoire courant.
compose_cmd = docker compose --file=$(CURDIR)/docker-compose.yml $(1)
# compose_up est une fonction qui utilise compose_cmd pour exécuter 'docker compose up'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose up'.
# L'option '--build' est toujours incluse, ce qui signifie que Docker construira les images avant de démarrer les conteneurs.
compose_up = $(call compose_cmd, up --build $(1))
# compose_run est une fonction qui utilise compose_cmd pour exécuter 'docker compose run'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose run'.
# L'option '--user=0' est toujours incluse, ce qui signifie que les commandes seront exécutées en tant que root à l'intérieur du conteneur.
compose_run = $(call compose_cmd, run --user=0 $(1))
# La cible par défaut. Elle nettoie le projet, le construit et le vérifie.
all: clean build verify
# Construire le projet à l'intérieur d'un conteneur Docker.
# Cette rÚgle Makefile utilise la fonction compose_up définie précédemment pour exécuter 'docker compose up' avec l'option '--exit-code-from=build'.
# L'option '--exit-code-from=build' signifie que la commande 'docker compose up' renverra le code de sortie du service 'build'.
# Si le service 'build' se termine avec un code de sortie non nul (ce qui signifie qu'une erreur s'est produite), alors 'docker compose up' se terminera également avec un code de sortie non nul.
# Cela permet à Make de savoir si la construction du projet a réussi ou non.
build:
@$(call compose_up,--exit-code-from=build build)
# Vérifier le projet. Actuellement désactivé.
verify:
@echo "Vérification désactivée"
# Démarrer les services 'serve' et 'qrcode'.
serve:
@$(call compose_up, --force-recreate serve qrcode)
# Démarrer un shell à l'intérieur du conteneur du service 'serve'.
shell:
@$(call compose_run,--entrypoint=sh --rm serve)
# Mettre à jour le fichier de verrouillage pour les dépendances npm.
dependencies-lock-update:
@$(call compose_run,--entrypoint=npm --rm serve install --package-lock)
# Mettre à jour les dépendances npm et le fichier de verrouillage.
dependencies-update:
@$(call compose_run,--entrypoint=ncu --workdir=/app/npm-packages --rm serve -u)
@make -C $(CURDIR) dependencies-lock-update
# Construire une version PDF du projet.
pdf:
@$(call compose_up, --exit-code-from=pdf pdf)
# Nettoyer le projet en arrĂȘtant et en supprimant les conteneurs Docker, les rĂ©seaux et les volumes.
clean:
@$(call compose_cmd, down -v --remove-orphans)
@rm -rf $(DIST_DIR) 2>/dev/null || { \
echo "NOTE: dist/ contient des fichiers appartenant Ă root, suppression depuis un conteneur"; \
docker run --rm --volume $(DIST_DIR):/dist alpine:3 \
sh -c 'rm -rf /dist/* /dist/.[!.]*' >/dev/null 2>&1; \
rm -rf $(DIST_DIR); \
}
# Démarrer le service 'qrcode'.
qrcode:
@$(call compose_up, qrcode)
# Déclarer des cibles factices.
.PHONY: all build verify serve shell clean qrcode pdf dependencies-update dependencies-lock-update# Les conteneurs de construction écrivent dans dist/ à travers un montage bind :
# ils doivent donc tourner sous l'utilisateur qui lance make. Laissés en root, ils
# y déposent des fichiers que `clean` ne peut plus supprimer depuis l'hÎte, et
# `make all` meurt sur sa premiÚre cible. Calculé ici plutÎt qu'écrit en dur : cet
# identifiant n'est pas le mĂȘme sur un poste, dans un Codespace ou sur un agent
# d'intégration continue.
CURRENT_UID ?= $(shell id -u):$(shell id -g)
export CURRENT_UIDVoilĂ d’oĂč vient le CURRENT_UID que docker-compose.yml consomme quelques diapos plus haut.
Compose ne le calcule pas, et ne se plaint pas non plus s’il manque : c’est au Makefile de le poser.
$(shell id -u):$(shell id -g) le lit Ă chaque appel, au lieu de le figer dans .env.
# Activer Docker BuildKit pour une construction plus rapide et la mise en cache des images.
DOCKER_BUILDKIT ?= 1
COMPOSE_DOCKER_CLI_BUILD ?= 1
export DOCKER_BUILDKIT COMPOSE_DOCKER_CLI_BUILDCette configuration de docker compose concerne Docker BuildKit, une fonctionnalité de Docker qui améliore les performances de construction des images Docker.
La ligne DOCKER_BUILDKIT ?= 1 vĂ©rifie si la variable d’environnement DOCKER_BUILDKIT est dĂ©jĂ dĂ©finie.
Si ce n’est pas le cas, elle lui attribue la valeur 1, ce qui active Docker BuildKit.
De mĂȘme, COMPOSE_DOCKER_CLI_BUILD ?= 1 vĂ©rifie si la variable d’environnement COMPOSE_DOCKER_CLI_BUILD est dĂ©jĂ dĂ©finie.
Si ce n’est pas le cas, elle lui attribue la valeur 1.
Cette variable d’environnement est utilisĂ©e pour activer l’utilisation de Docker CLI lors de l’utilisation de Docker Compose.
C’est nĂ©cessaire car Docker Compose ne supporte pas BuildKit par dĂ©faut, donc cette variable d’environnement est une solution de contournement pour l’activer. En rĂ©sumĂ©, ces deux lignes de code activent Docker BuildKit pour amĂ©liorer les performances de construction des images Docker lors de l’utilisation de Docker Compose.
# Définition des commandes shell réutilisables pour Docker Compose.
# compose_cmd est une fonction qui exécute la commande 'docker compose' avec le fichier docker-compose.yml du répertoire courant.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose'.
# $(CURDIR) est une variable d'environnement dans le Makefile qui représente le répertoire courant dans lequel
# le Makefile est exécuté. C'est une fonctionnalité intégrée de GNU Make. Elle est souvent utilisée pour référencer
# des fichiers ou des répertoires relatifs au répertoire courant.
compose_cmd = docker compose --file=$(CURDIR)/docker-compose.yml $(1)
# compose_up est une fonction qui utilise compose_cmd pour exécuter 'docker compose up'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose up'.
# L'option '--build' est toujours incluse, ce qui signifie que Docker construira les images avant de démarrer les conteneurs.
compose_up = $(call compose_cmd, up --build $(1))
# compose_run est une fonction qui utilise compose_cmd pour exécuter 'docker compose run'.
# Elle prend un argument $(1) qui représente les options supplémentaires à passer à la commande 'docker compose run'.
# L'option '--user=0' est toujours incluse, ce qui signifie que les commandes seront exécutées en tant que root à l'intérieur du conteneur.
compose_run = $(call compose_cmd, run --user=0 $(1))# Construire le projet à l'intérieur d'un conteneur Docker.
# Cette rÚgle Makefile utilise la fonction compose_up définie précédemment pour exécuter 'docker compose up' avec l'option '--exit-code-from=build'.
# L'option '--exit-code-from=build' signifie que la commande 'docker compose up' renverra le code de sortie du service 'build'.
# Si le service 'build' se termine avec un code de sortie non nul (ce qui signifie qu'une erreur s'est produite), alors 'docker compose up' se terminera également avec un code de sortie non nul.
# Cela permet à Make de savoir si la construction du projet a réussi ou non.
build:
@$(call compose_up,--exit-code-from=build build)Cette rĂšgle Makefile est utilisĂ©e pour construire le projet Ă l’intĂ©rieur d’un conteneur Docker.
Elle utilise la fonction compose_up pour exĂ©cuter la commande docker compose up avec l’option --exit-code-from=build.
Cette option permet à la commande docker compose up de renvoyer le code de sortie du service build, ce qui permet à Make de savoir si la construction du projet a réussi ou non.


Comprendre et appliquer les bonnes pratiques de sécurité Docker
Optimiser les conteneurs pour la production
GĂ©rer les registres d’images Docker
Déployer une application sécurisée et production-ready
SecureVote : Une application de vote en ligne moderne
Frontend React (interface de vote)
Backend API Python Flask
Base de données PostgreSQL
Redis pour le cache
Nginx comme reverse proxy
DépÎt GitHub : https://github.com/gounthar/securevote
services:
frontend: # React app
backend: # Flask API
database: # PostgreSQL
cache: # Redis
proxy: # NginxArchitecture multi-tiers classique pour DevOps
Phase 1 (30 min) : DĂ©marrer l’application "dangereuse"
Phase 2 (1h15) : SĂ©curiser l’application
Phase 3 (1h) : Optimiser pour la production
| L’application initiale contient de nombreuses vulnĂ©rabilitĂ©s volontaires ! |
git clone https://github.com/gounthar/securevote.git
cd securevote/phase1
docker compose up -d
# Vérifier l'état des services
docker compose ps
# Suivre les logs (Ctrl+C pour quitter)
docker compose logs -fAttendre que tous les services soient UP (~30 secondes)
Accédez à http://localhost:8080
Testez l’application de vote
Explorez les fichiers et identifiez les problĂšmes :
Qui exécute les conteneurs ?
OĂč sont stockĂ©s les secrets ?
Les images sont-elles Ă jour ?
Y a-t-il des ports exposés inutilement ?
à vérifier :
docker compose ps
docker container inspect <container>
Contenu des Dockerfile
Variables d’environnement
Questions : Quels risques identifiez-vous ? Comment exploiter ces failles ?
Les 3 piliers de la sécurité Docker :
Images sûres : Bases fiables, sans vulnérabilités
Runtime sécurisé : Isolation, utilisateurs non-root
Secrets protégés : Pas de mots de passe en clair
Un conteneur ne devrait avoir QUE les permissions nécessaires à son fonctionnement
Pas d’exĂ©cution en tant que root
Capacités Linux minimales
SystĂšme de fichiers en lecture seule quand possible
Réseau isolé
Les images Docker peuvent contenir des vulnérabilités connues (CVE)
Outils de scan :
Docker Scout (intégré à Docker Desktop)
Trivy (open source, trĂšs populaire)
Snyk, Grype
# Scanner une image locale
docker scout cves python:3.11
# Comparer deux images
docker scout compare python:3.11 --to python:3.11-slim| Démonstration live recommandée |
Méthode rapide (pour le TP) :
# Installation directe du binaire
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin
trivy --versioncurl | sudo sh est pratique MAIS dangereux en production ! |
Méthode sécurisée (recommandée) :
# 1. Télécharger le script
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh -o /tmp/trivy-install.sh
# 2. Inspecter le contenu
less /tmp/trivy-install.sh
# 3. Exécuter aprÚs vérification
sudo sh /tmp/trivy-install.sh -b /usr/local/binSi vous n’avez pas les droits sudo :
# Via Docker (aucune installation nécessaire)
docker container run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $HOME/.cache/trivy:/root/.cache \
aquasec/trivy:0.54.1 image python:3.11Pin la version (:0.54.1) et persist le cache (-v $HOME/.cache) pour performances |
Autres systĂšmes :
macOS : brew install trivy
Debian/Ubuntu stable : Via apt (voir documentation)
trivy image python:3.11
Total: 145 vulnerabilities (52 HIGH, 93 MEDIUM)
ââââââââââââŹâââââââââââââââŹâââââââââââŹââââââââââ
â Library â Vulnerabilityâ Severity â Version â
â openssl â CVE-2023-XXX â HIGH â 1.1.1n â
â curl â CVE-2023-YYY â MEDIUM â 7.68.0 â
ââââââââââââŽâââââââââââââââŽâââââââââââŽââââââââââAction : Mettre Ă jour, changer d’image de base, appliquer des patches
ProblĂšme : Par dĂ©faut, les processus s’exĂ©cutent en tant que root
FROM python:3.11
COPY app.py /app/
CMD ["python", "/app/app.py"] # Root !| Si compromis, l’attaquant a les privilĂšges root ! |
FROM python:3.11-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY app.py /app/
RUN chown -R appuser:appuser /app
USER appuser
CMD ["python", "/app/app.py"]Toujours créer un utilisateur dédié
UID > 1000 (éviter conflits systÚme)
Ne jamais revenir Ă root aprĂšs USER
Vérifier : docker container exec <container> id
Linux numérote les utilisateurs (User ID) :
UID 0 : root (super-utilisateur)
UID 1-999 : comptes systĂšme (www-data, postgres, redis…)
UID â„ 1000 : utilisateurs normaux et applications
| Avec volumes montés, UID < 1000 peut créer des conflits de sécurité ! |
# RISQUĂ : UID fixe < 1000
RUN useradd -u 33 appuser
# Conflit possible si l'hĂŽte a www-data (UID 33) !
# SĂR : UID > 1000
RUN useradd -u 1001 appuser
# OU laisser le systĂšme choisir (sera â„ 1000)
RUN useradd appuserVérifier : docker container exec <container> id
Mauvaise pratique :
environment:
- POSTGRES_PASSWORD=super_secret_123 # NON !Visible dans docker container inspect, logs, historique ! |
Ătape 1 : CrĂ©er le fichier .env
# .env (Ă ne JAMAIS commiter)
DB_PASSWORD=super_secret_123Ătape 2 : ProtĂ©ger avec .gitignore
# .gitignore
.env
secrets/Ătape 3 : Utiliser dans docker-compose.yml
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
API_KEY: ${API_KEY:-default_key} # Valeur par défaut si absent
DB_HOST: ${DB_HOST?required} # Erreur si non défini| Docker Compose charge automatiquement le fichier .env |
Syntaxes avancées :
${VAR:-default} : Utilise default si VAR n’est pas dĂ©finie
${VAR?error message} : Ăchoue avec message si VAR manquante
Utile pour séparer config obligatoire vs optionnelle
Gestion native dans Docker Swarm
Secrets cryptés dans le cluster Swarm
Montés en RAM dans /run/secrets/
AccÚs contrÎlé par service
echo "secret123" | docker secret create db_password -
docker service create --secret db_password postgresSolution d’entreprise pour secrets
Serveur centralisé avec API REST
Rotation automatique des credentials
Audit complet et secrets dynamiques
# Application interroge Vault via API
vault kv get -field=password secret/database/prod| Nécessite infrastructure dédiée (serveur Vault) |
Services managés AWS / Azure / GCP
AWS Secrets Manager : intégration RDS/Aurora
Azure Key Vault : intégration Azure AD, HSM
GCP Secret Manager : intégration Cloud Run/GKE
â EntiĂšrement managĂ©, haute disponibilitĂ© â Vendor lock-in, coĂ»t Ă l’usage
Passage direct via environnement
Simple pour dev et CI/CD
Secrets injectés au runtime
# En une ligne
DB_PASSWORD="secret123" docker compose up -d
# Ou via export
export DB_PASSWORD="secret123"
docker compose up -dVisible dans ps aux et historique shell ! |
| Solution | ComplexitĂ© | CoĂ»t | Cas d’usage |
|---|---|---|---|
Fichiers .env | âȘ Faible | đ° Gratuit | Dev, petits projets |
Docker Secrets | đĄ Moyenne | đ° Gratuit | Docker Swarm prod |
Vault | đŽ ĂlevĂ©e | đ°đ° Variable | Grandes organisations |
Cloud Secrets | đĄ Moyenne | đ°đ° Payant | DĂ©jĂ sur cloud |
Variables shell | âȘ Faible | đ° Gratuit | Dev local, CI/CD |
Ă la fin, le secret est dans le conteneur :
en variable d’environnement (.env, variables shell)
en fichier sous /run/secrets/ (Docker Secrets)
ou en mĂ©moire, lu par l’application via une API (Vault, cloud)
dans tous les cas, le programme a la valeur en clair
Et si le programme ne recevait jamais le secret ?
sbx v0.45.1. Outil rĂ©cent : les commandes peuvent changer.Sur l’hĂŽte, un secret factice liĂ© Ă un seul domaine :
sbx policy init balanced
sbx secret set-custom --host httpbin.org --env DEMO_TOKEN --value valeur-reelle-576
sbx create shell --name demo .
sbx policy allow network httpbin.orgDans la sandbox :
sbx exec demo sh -c 'echo $DEMO_TOKEN'
# sbx-cs-oQlw2y1oLoOzfg5D
sbx exec demo sh -c 'curl -sS -H "Authorization: Bearer $DEMO_TOKEN" https://httpbin.org/headers'
# "Authorization": "Bearer valeur-reelle-576"Le programme peut toujours utiliser le secret vers l’hĂŽte dĂ©clarĂ©
Un hĂŽte qui renvoie l’en-tĂȘte le rĂ©vĂšle : httpbin.org vient de le faire !
Le proxy déchiffre le HTTPS : il installe sa propre autorité de certification dans la sandbox
Le secret rĂ©el reste sur l’hĂŽte : sans trousseau, dans un fichier
| Le proxy garde la valeur hors de la sandbox, tant que l’hĂŽte dĂ©clarĂ© ne la renvoie pas. Il n’empĂȘche jamais de l’utiliser. |
Transformer SecureVote en application sécurisée
Images sûres et légÚres
Scanner les vulnérabilités avec Trivy
Utilisateurs non-root
Protéger les secrets
Isoler les réseaux
Mission :
Scanner les images avec Docker Scout ou Trivy
Identifier les vulnérabilités critiques
Remplacer par des images -slim ou -alpine
Re-scanner et comparer
docker scout cves securevote-backend:latest
# Modifier Dockerfile â python:3.11-slim
docker compose build backend
docker scout cves securevote-backend:latestMission :
Modifier tous les Dockerfiles pour des utilisateurs non-root
Backend Python : créer flaskuser
Frontend React : créer reactuser
Nginx : utiliser nginx-unprivileged
FROM python:3.11-slim
RUN groupadd -r flaskuser && useradd -r -g flaskuser flaskuser
USER flaskuserdocker compose exec backend ps aux
# Le processus principal (PID 1) doit tourner en tant que flaskuser
docker compose exec frontend ps aux
# Le processus principal (PID 1) doit tourner en tant que reactuserSi vous voyez UID 0 ou root pour le processus principal, c’est Ă corriger ! |
Mission :
Identifier les secrets en clair dans docker-compose.yml
Créer .env (sans le commiter !)
Ajouter .env au .gitignore
Utiliser les variables : ${DB_PASSWORD}
Créer .env.example comme template
Vérification collective :
Scans : moins de vulnérabilités ?
Aucun conteneur en root ?
Aucun secret en clair versionné ?
Application fonctionne ?
docker compose ps
docker compose exec backend ps aux
trivy image securevote-backend:latest --severity HIGH,CRITICALDéveloppement :
Ressources illimitées
Logs verbeux
Redémarrages manuels
Production :
Ressources limitées
Logs structurés
Auto-healing
Pourquoi limiter ?
EmpĂȘcher monopolisation du serveur
Garantir stabilité
Prévoir capacité
Détecter fuites mémoire
| Sans limites â 100% CPU/RAM ! |
services:
backend:
deploy:
resources:
limits:
cpus: '0.5' # Max 50% CPU
memory: 512M # Max 512 Mo RAM
reservations:
cpus: '0.25' # Min garanti
memory: 256MDémarrer sans limites
Observer : docker container stats
Ajouter 20-30% de marge
Tester sous charge
Ajuster
services:
backend:
restart: no # Jamais
restart: always # Toujours
restart: on-failure # Si erreur
restart: unless-stopped # Sauf arrĂȘt manuelProduction : PrivilĂ©gier unless-stopped ou on-failure
services:
backend:
restart: on-failurePour limiter le nombre de tentatives en Docker Swarm, utilisez deploy.restart_policy.max_attempts: 5 |
Ăvite les boucles infinies de redĂ©marrage
services:
backend:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s@app.route('/health')
def health():
try:
db.ping()
return jsonify({"status": "healthy"}), 200
except:
return jsonify({"status": "unhealthy"}), 503services:
backend:
depends_on:
database:
condition: service_healthy
cache:
condition: service_startedservice_started : DĂ©marrĂ© (pas forcĂ©ment prĂȘt)
service_healthy : Healthcheck validé
service_completed_successfully : Terminé avec succÚs
Observabilité :
Logs : Que s’est-il passĂ© ?
MĂ©triques : Combien de CPU/RAM/requĂȘtes ?
Traces : Quel chemin a pris une requĂȘte ?
| TP13 a déjà couvert ELK |
services:
backend:
logging:
driver: "json-file"
options:
max-size: "10m" # Max 10 Mo
max-file: "3" # Garder 3 fichiers| Sans limites, les logs remplissent le disque ! |
Mauvais : Server started on port 5000
Bon :
{"timestamp":"2025-01-15T10:30:00Z","level":"INFO","service":"backend","message":"Server started","port":5000}Facilement parsables par ELK, Loki, etc.
Optimiser SecureVote pour la production
Limites de ressources
Politiques de redémarrage
Health checks
Optimiser les logs
Tester la résilience
Mission :
Observer avec docker container stats
Définir limites appropriées
Tester sous charge (script fourni)
Ajuster
docker container stats --no-stream
./load_test.sh
docker container statsMission :
Ajouter politiques de redémarrage
Créer endpoint /health dans backend
Configurer healthchecks
Tester en tuant un conteneur
docker compose kill backend
docker compose ps
docker compose logs backendMission :
Configurer depends_on avec conditions
Tester ordre de démarrage
Simuler indisponibilité DB
Vérifier backend ne démarre pas sans DB
Application production-ready :
Limites de ressources â
Auto-healing â
Health checks â
DĂ©pendances ordonnĂ©es â
Logs optimisĂ©s â
docker compose config --services
docker compose ps
docker container stats --no-streamServeur de stockage d’images Docker
Ăquivalent de npm pour Node, PyPI pour Python
Public (Docker Hub) ou privé (self-hosted)
Gestion des versions (tags)
docker image pull nginx:latest
# Ăquivalent Ă :
docker image pull docker.io/library/nginx:latestGratuit pour images publiques
Limites de rate (pull anonyme)
Pourquoi ?
Images propriétaires
ContrĂŽle d’accĂšs
Compliance et sécurité
Pas de limite de rate
Docker Registry (open source)
Harbor (CNCF, avec scan)
AWS ECR, Azure ACR, GCP GCR
GitLab / GitHub Container Registry
docker container run -d -p 5000:5000 --name registry registry:2
docker image tag securevote-backend:latest localhost:5000/securevote-backend:latest
docker image push localhost:5000/securevote-backend:latest# Bonne pratique : versionner
docker image tag myapp:latest myregistry.com/myapp:1.2.3
docker image tag myapp:latest myregistry.com/myapp:1.2
docker image tag myapp:latest myregistry.com/myapp:latest| Plusieurs tags â mĂȘme image |
Si le temps le permet :
Démarrer registre local (port 5000)
Tagger images SecureVote
Pousser vers registre
Modifier docker-compose.yml
Re-déployer
â Images officielles et Ă jour
â Scan rĂ©gulier des vulnĂ©rabilitĂ©s
â Utilisateurs non-root systĂ©matiques
â Secrets jamais en clair
â RĂ©seau isolĂ© par dĂ©faut
â Limites de ressources CPU/RAM
â Politiques de redĂ©marrage
â Health checks implĂ©mentĂ©s
â DĂ©pendances ordonnĂ©es
â Logs structurĂ©s et rotation
Sécuriser une application Docker de bout en bout
Scanner et corriger les vulnérabilités
Gérer les secrets correctement
Configurer pour la production
Distribuer via registres
Avant :
Images non scannées
Root partout
Secrets en clair
Pas de limites
AprĂšs :
Images slim, scannées
Utilisateurs dédiés
Secrets protégés
Production-ready â
Ătape 1 : VĂ©rifier les logs
docker container logs nom-conteneur
# OU pour suivre en temps réel
docker container logs -f nom-conteneurĂtape 2 : Inspecter le conteneur
docker container inspect nom-conteneur
# Voir seulement l'état
docker container inspect --format='{{.State.Status}}' nom-conteneur
# Voir le code de sortie
docker container inspect --format='{{.State.ExitCode}}' nom-conteneurProblĂšmes courants :
| SymptĂŽme | Cause probable | Solution |
|---|---|---|
| NGINX : résolution DNS statique | Utiliser variables : |
| Port dĂ©jĂ utilisĂ© | Changer port ou arrĂȘter service conflictuel |
| Volume mal configuré | Vérifier chemin absolu dans volumes |
| ProblÚme permissions fichier | Vérifier propriétaire et chmod |
Ătape 1 : VĂ©rifier le status des services
docker compose ps
# Voir seulement les unhealthy
docker compose ps | grep unhealthyĂtape 2 : Inspecter le health check
docker container inspect nom-conteneur | grep -A 20 Health
# Voir uniquement le status
docker container inspect --format='{{.State.Health.Status}}' nom-conteneur
# Voir les derniers logs du health check
docker container inspect --format='{{json .State.Health.Log}}' nom-conteneur | jqCodes de sortie importants :
0 : Health check rĂ©ussi â
1 : Health check échoué (erreur applicative)
127 : Commande non trouvĂ©e (outil manquant dans l’image)
137 : Tué par signal (timeout)
ProblĂšmes courants :
ExitCode: 127
Output: "/bin/sh: curl: not found"
â Solution : Installer curl dans DockerfileExitCode: 1
Output: "Connection refused"
â Solution : VĂ©rifier que l'app Ă©coute sur le bon portSurveiller tous les Ă©vĂ©nements Docker en temps rĂ©el :
# Voir tous les événements
docker events
# Filtrer par conteneur
docker events --filter container=nom-conteneur
# Filtrer par type d'événement
docker events --filter event=start
docker events --filter event=die
docker events --filter event=health_status
# Combiner plusieurs filtres
docker events --filter container=backend --filter event=health_statusExemple de sortie :
2026-01-12T10:30:15 container health_status: unhealthy (name=backend)
2026-01-12T10:30:20 container die (name=backend, exitCode=137)
2026-01-12T10:30:25 container start (name=backend)Utilisation pratique :
# Dans un terminal, monitorer les événements
docker events --filter event=health_status
# Dans un autre terminal, démarrer les services
docker compose up -d
# Observer en temps réel les changements de santéQuand un problÚme survient en production :
â
docker compose ps â Identifier les services en erreur
â
docker container logs <service> â Lire les logs d’erreur
â
docker container inspect <service> â VĂ©rifier la configuration
â
Si unhealthy : docker container inspect pour voir Health.Log
â
docker events en parallÚle pour monitoring temps réel
Commande magique de debugging : Affiche : statuts, logs récents, et écoute les événements ! |
Cosign (projet Sigstore) vĂ©rifie qui a signĂ© une image, pas seulement qu’elle est signĂ©e :
docker container run --rm ghcr.io/sigstore/cosign/cosign:v3.1.3 verify \
--certificate-identity=krel-trust@k8s-releng-prod.iam.gserviceaccount.com \
--certificate-oidc-issuer=https://accounts.google.com \
registry.k8s.io/pause:3.10Verification for registry.k8s.io/pause:3.10 --
The following checks were performed on each of these signatures:
- The cosign claims were validated
- Existence of the claims in the transparency log was verified offline
- The code-signing certificate was verified using trusted certificate authority certificatesAvec --certificate-identity=someone@example.com, la commande échoue (code de sortie 12) : no matching signatures.
Orchestration : Kubernetes, Docker Swarm
Secrets Management : Vault, Sealed Secrets
Image Signing : Cosign (Sigstore), Notation
Runtime Security : Falco, Aqua Security
Docker Security: https://docs.docker.com/engine/security/
CIS Docker Benchmark
OWASP Docker Security Cheat Sheet
Bravo, vous avez transformé SecureVote en application sécurisée et production-ready !






docker image history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7⊠1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1⊠1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c⊠1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -⊠63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21⊠69.2MBdocker image history nginx
IMAGE CREATED CREATED BY SIZE COMMENT
f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7⊠1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1⊠1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c⊠1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -⊠63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21⊠69.2MBIl est préférable de limiter le nombre de couches pour limiter la bande passante.
RUN apk add --no-cache \
patch \
tar \
mercurial \
git \
ruby \
ruby-dev \
ruby-bundler \
make \
g++ \
zlib-dev \
libxml2-dev \
docker \
nodejs \
npm \
&& mkdir -p /var/lib/docker-builder \
&& mkdir -p /etc/docker-builder && apk del make g++ zlib-dev libxml2-dev ruby-dev \
&& rm -f previously-downloaded.tar.gz \
&& rm -rf /tmp/*Il est utile, pour des raisons d’espace, de nettoyer le filesystem de ses futurs containers, en donnant les bonnes instructions dans le Dockerfile.
Avec apk add --no-cache, l’index des paquets n’est jamais Ă©crit dans l’image : il n’y a pas de cache Ă vider ensuite.
Les mots clés EXPOSE et LABEL peuvent fournir de précieuses informations aux utilisateurs de vos images !
(les ports exposĂ©s par dĂ©faut, les auteurs de l’image, la version d’un middleware embarquĂ©, âŠ)
Il est possible de lister toutes les modifications qui ont Ă©tĂ© apportĂ©es au filesystem d’un container.
docker container diff 29f1c4
C /run
A /run/nginx.pid
C /var
C /var/lib
C /var/lib/nginx
C /var/lib/nginx/tmp
A /var/lib/nginx/tmp/client_body
A /var/lib/nginx/tmp/fastcgi
A /var/lib/nginx/tmp/proxy
A /var/lib/nginx/tmp/scgi
A /var/lib/nginx/tmp/uwsgi
C /var/log
C /var/log/nginx
A /var/log/nginx/access.log
A /var/log/nginx/error.logUne bonne piste pour savoir quels volumes déclarer !
Savoir que mon PID 1 est toujours en cours d’exĂ©cution n’est peut-ĂȘtre pas la meilleure piste pour savoir si mon conteneur est en bonne santĂ© !
FROM ghost:3
RUN apt update && apt install curl -y \
&& rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=1m --timeout=30s --retries=3 CMD curl --fail http://localhost:2368 || exit 1
Les logs
des containers
du démon Docker
docker container exec
docker container inspect
docker container cp
docker image history
docker container statsrappel : docker container logs permet de lister les logs des conteneurs.
docker container logs 47d6
Fri Nov 20 00:39:52 UTC 2023
Fri Nov 20 00:39:53 UTC 2023Par défaut, les logs des conteneurs sont stockés dans des fichiers json. Mais comment faire pour les envoyer vers un concentrateur ?
docker container run -d --log-driver=gelf --log-opt gelf-address=udp://localhost:12201 -p 88:80 nginx
on spécifie un driver
on configure le driver
Ătapes
Lancer une stack ELK grĂące aux fichiers fournis dans le repo https://gitlab.univ-artois.fr/bruno.verachten/devops-docker-tp13
Alimenter en logs avec la commande docker container run --log-driver=gelf --log-opt gelf-address=udp://localhost:12201 alpine sh -c 'seq 1 100'
Alpine n’a pas |
CrĂ©er un index "timestamp" sur Kibana puis aller Ă l’Ă©cran "discover"
Lancer un conteneur nginx et concentrez ses logs dans ELK
Dependabot est un outil qui vous aide à maintenir vos dépendances à jour.
Il ouvre automatiquement des merge requests pour les mises à jour de dépendances dans vos projets GitLab.
Dependabot peut analyser vos Dockerfiles et vos fichiers docker-compose pour trouver les dĂ©pendances qui peuvent ĂȘtre mises Ă jour.
Il crée ensuite des merge requests pour chaque mise à jour de dépendance.
Garder vos dépendances à jour est crucial pour la sécurité et la stabilité de vos applications.
Dependabot automatise ce processus, vous faisant gagner du temps et rĂ©duisant le risque d’oublier une mise Ă jour importante.
Dependabot n’est pas magique, il faut que votre CI soit capable de construire votre application avec les nouvelles dĂ©pendances.
Ce qui n’est pas testĂ© ne fonctionne pas.
Malheureusement, il n’y a pas encore de support officiel pour GitLab dans Dependabot (et vice versa).
Notre forge Gitlab est définie dans un fichier docker-compose.yml, et Dependabot aussi.
Dependabot doit avoir accĂšs Ă notre forge GitLab pour pouvoir ouvrir des merge requests.
Suivons donc la documentation officielle.
| Notez bien le token, vous ne le reverrez plus jamais. |
L’Ă©tape suivante, c’est de positionner les valeurs de certaines variables d’environnement.
Ăa peut ĂȘtre dans un .env, ou dans les variables d’environnement de votre machine.
export SETTINGS__GITLAB_URL=http://localhost
export SETTINGS__GITLAB_ACCESS_TOKEN=glpat-XXXXXXXXXXXXXXXXXXXXDĂ©marrez l’application avec docker compose:
curl -s https://gitlab.com/dependabot-gitlab/dependabot/-/raw/v3.8.0-alpha.1/docker-compose.yml | docker compose -f - up -d
đą
Vous vous souvenez du chapitre sur les réseaux Docker ?
Dans Docker, chaque conteneur a son propre espace de nom rĂ©seau, ce qui signifie que localhost Ă l’intĂ©rieur d’un conteneur fait rĂ©fĂ©rence au conteneur lui-mĂȘme, et non Ă la machine hĂŽte ou Ă d’autres conteneurs.
Lorsque vous essayez de faire un ping sur gitlab-instance-gitlab-1 depuis le conteneur web, il ne connaĂźt pas le nom d’hĂŽte gitlab-instance-gitlab-1 car il n’est pas dans le mĂȘme espace de nom rĂ©seau.
Pour permettre aux conteneurs de communiquer entre eux, ils doivent ĂȘtre dans le mĂȘme rĂ©seau Docker.
Lorsque vous utilisez Docker Compose, il crĂ©e automatiquement un rĂ©seau par dĂ©faut pour votre application et tout service dĂ©fini dans le docker-compose.yml peut atteindre les autres en utilisant le nom du service comme nom d’hĂŽte.
Dans notre cas, le conteneur web et le conteneur gitlab-instance-gitlab-1 ne sont pas dans le mĂȘme rĂ©seau Docker.
Vous pouvez vĂ©rifier cela en inspectant les rĂ©seaux de chaque conteneur Ă l’aide de la commande docker container inspect.
S’ils ne sont pas dans le mĂȘme rĂ©seau, nous pouvons crĂ©er un rĂ©seau et ajouter les deux services Ă celui-ci dans notre fichier docker-compose.yml.
Voici un exemple :
version: '3'
services:
web:
image: web
networks:
- mynetwork
gitlab-instance-gitlab-1:
image: gitlab
networks:
- mynetwork
networks:
mynetwork:AprÚs avoir mis à jour votre fichier docker-compose.yml, vous devez recréer vos conteneurs pour que les modifications prennent effet.
Vous pouvez le faire avec la commande docker compose up -d --force-recreate.
AprĂšs cela, vous devriez pouvoir faire un ping sur gitlab-instance-gitlab-1 depuis le conteneur web.
web:
image: *base_image
networks:
[...]
networks:
mynetwork:Et…
gitlab:
# The Docker image to use for the GitLab server
image: 'gitlab/gitlab-ce:16.6.0-ce.0'
networks:
- mynetwork
[...]
networks:
mynetwork:Sauf que…
root@95def7cb933d:/home/dependabot/app# nmap -sn 172.30.0.0/16
Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-21 20:45 UTC
Nmap scan report for 172.30.0.1
Host is up (0.0000090s latency).
MAC Address: 02:42:50:A6:7C:5A (Unknown)
Nmap scan report for symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2)
Host is up (0.000012s latency).
MAC Address: 02:42:AC:1E:00:02 (Unknown)
Nmap scan report for symbiosis-gitlab-runner-1-1.symbiosis_mynetwork (172.30.0.3)
Host is up (0.000015s latency).
MAC Address: 02:42:AC:1E:00:03 (Unknown)
Nmap scan report for symbiosis-gitlab-runner-2-1.symbiosis_mynetwork (172.30.0.4)
Host is up (0.000032s latency).
MAC Address: 02:42:AC:1E:00:04 (Unknown)
Nmap scan report for symbiosis-redis-1.symbiosis_mynetwork (172.30.0.5)
Host is up (0.0000070s latency).
MAC Address: 02:42:AC:1E:00:05 (Unknown)
Nmap scan report for symbiosis-mongodb-1.symbiosis_mynetwork (172.30.0.6)
Host is up (0.000016s latency).
MAC Address: 02:42:AC:1E:00:06 (Unknown)
Nmap scan report for symbiosis-docker-1.symbiosis_mynetwork (172.30.0.7)
Host is up (0.000021s latency).
MAC Address: 02:42:AC:1E:00:07 (Unknown)
Nmap scan report for symbiosis-worker-1.symbiosis_mynetwork (172.30.0.9)
Host is up (0.000024s latency).
MAC Address: 02:42:AC:1E:00:09 (Unknown)
Nmap scan report for 95def7cb933d (172.30.0.10)
Host is up.Victoire?
docker compose -f docker-compose-dependabot.yml exec -it -u root web b
ash
root@95def7cb933d:/home/dependabot/app# ping symbiosis-gitlab-1.symbiosis_mynetwork
PING symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2) 56(84) bytes of data.
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=1 ttl=64 time=0.165 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=2 ttl=64 time=0.053 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=3 ttl=64 time=0.039 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=4 ttl=64 time=0.040 ms
64 bytes from symbiosis-gitlab-1.symbiosis_mynetwork (172.30.0.2): icmp_seq=5 ttl=64 time=0.056 ms
^C
--- symbiosis-gitlab-1.symbiosis_mynetwork ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4165ms
rtt min/avg/max/mdev = 0.039/0.070/0.165/0.047 ms
On y est presque!
rappel : docker container exec permet de lancer une commande dans un container
docker container exec <containerID> echo "hello"docker container exec -it <containerID> bashrappel : docker container inspect et docker image inspect listent toutes les caractĂ©ristiques d’un conteneur ou d’une image.
On peut filtrer le retour de la commande avec jq ou l’option --format.
Cette commande permet d’Ă©changer des fichiers entre un conteneur et la machine hĂŽte.
docker container cp --help
Usage: docker container cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
Copy files/folders between a container and the local filesystem
Aliases:
docker container cp, docker cp
Options:
-a, --archive Archive mode (copy all uid/gid information)
-L, --follow-link Always follow symlinks in SRC_PATH
-q, --quiet Suppress progress output during copyCette commande permet d’afficher la concatĂ©nation de tous les Dockerfiles qui ont abouti Ă cette image.
docker image history nginx
IMAGE CREATED CREATED BY SIZE f35646e83998 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) STOPSIGNAL SIGTERM 0B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["/docker-entr⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:0fd5fca330dcd6a7⊠1.04kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:1d0a4127e78a26c1⊠1.96kB
<missing> 4 weeks ago /bin/sh -c #(nop) COPY file:e7e183879c35719c⊠1.2kB
<missing> 4 weeks ago /bin/sh -c set -x && addgroup --system -⊠63.6MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV PKG_RELEASE=1~buster 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NJS_VERSION=0.4.4 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.19.3 0B
<missing> 4 weeks ago /bin/sh -c #(nop) LABEL maintainer=NGINX Do⊠0B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:0dc53e7886c35bc21⊠69.2MBCette commande permet d’avoir les stats en temps rĂ©el d’un conteneur.
docker container stats 42f128
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
42f128 0.00% 1.454 MB/4.145 GB 0.04% 648 B/648 BNotre valeureux collĂšgue Jean-Michel s’initiait Ă l’art mystĂ©rieux de Docker, et gĂ©nĂ©reusement partageait ses crĂ©ations sous forme d’images Docker pour l’entreprise.
Michel a soudainement dĂ©crochĂ© le jackpot du Loto, laissant derriĂšre lui son bureau du jour au lendemain, alors qu’il Ă©tait sur le point de nous offrir une image Nginx.
DĂ©sormais, nous n’avons que le binaire de son chef-d’Ćuvre, accessible Ă cette adresse :
Rassurez-vous, on nous a dit que docker image load est la premiĂšre Ă©tape de la formule magique pour percer les secrets de son Ćuvre.
Ă vos claviers ! đ§ââïžâš
docker image load est la premiÚre étape de la formule magique
Okay…
đ§ââïžâš
docker image load -i bad-nginx.dkr
Loaded image: bad-nginx:1Nous voilĂ bien avancĂ©s…
On essaye de lancer un conteneur basé dessus�
docker container run -d --name bad-1 bad-nginx:1
f4caa4a641729c0ab3d7b5974fa735c128047f75292c7f16495b72c7c6808502C’est lancĂ©âŻ?
docker container ls --format '{{.Names}}' | grep "bad"Oups, il n’est pas lĂ …
Essayons encore.
docker container ls -a --format '{{.Names}}' | grep "bad"
bad-1C’est donc lancĂ©, mais il a crashĂ©.
Allons donc chercher les logs…
docker container logs bad-1
Error : nginx is not executableOn a trouvĂ© le problĂšmeâŻ!
Spoiler alert : on a trouvé UN problÚme, pas LE problÚme.
Je ne sais pas ce qu’a fait Jean-Michel, mais il a cassĂ© le binaire de Nginx.
Est-ce un problÚme de permission, de dépendance, de version, de compilation�
En tous cas, c’est cassĂ©.
Regardons un peu ce que nous dit docker image inspect de tout ça…
docker image inspect bad-nginx:1
[
{
"Id": "sha256:a469e4d1cb0c61324298fc6fde09f78f211420a1848b690dec35d9ac324a6cab",
"RepoTags": [
"bad-nginx:1"
],
[...]
"ContainerConfig": {
[...]
"ExposedPorts": {
"80/tcp": {}
},Port 80 exposĂ©, c’est bon signe.
[...]
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],On a un PATH, c’est bon signe.
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"/bin/sh\" \"-c\" \"nginx\"]"
],On a un CMD, c’est bon signe.
[...]
"Entrypoint": [
"/bin/sh",
"-c",
"/tmp/entrypoint.sh"
],On a un Entrypoint, c’est bon signe.
[...]
"Labels": {
[...]
"org.label-schema.name": "CentOS Base Image",
[...]
}
},
"DockerVersion": "18.09.0",
[...]
"Architecture": "amd64",
"Os": "linux",
[...]
}
] [...]
"Labels": {
[...]
"org.label-schema.name": "CentOS Base Image",
[...]
}
},
"DockerVersion": "18.09.0",
[...]
"Architecture": "amd64",
"Os": "linux",
[...]
}
]C’est bien une image Linux amd64 (donc pour ton PC, mais pas pour ton Mac M1, toi lĂ -bas), mais argh, c’est basĂ© sur CentOS, pas sur Alpine ou DebianâŻ!
Ăa a Ă©tĂ© construit avec une vieille version de Docker.
Qu’est-ce qu’on fait maintenantâŻ?
Tournons-nous vers l’histoireâŻ!

docker image history bad-nginx:1
IMAGE CREATED CREATED BY SIZE COMMENT
a469e4d1cb0c 3 years ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "ngin⊠0B
<missing> 3 years ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/sh" "-c⊠0B
<missing> 3 years ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 years ago /bin/sh -c #(nop) COPY file:b0fc89bcaf962602⊠98B
<missing> 3 years ago /bin/sh -c yum install -y nginx && yum clean⊠56.7MB
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc⊠0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:538afc0c5c964ce0d⊠215MBĂa se lit de bas en haut…
Le premier ADD est l’image de base, CentOS.
La deuxiĂšme ligne, c’est une mĂ©tadata, on s’en fiche un peu.
La troisiĂšme ligne, c’est le CMD de l’image de base, qui est bash.
La quatriĂšme ligne, c’est l’installation de Nginx par l’infĂąme yum de Centos (dĂ©jĂ repĂ©rĂ© dans le docker image inspect).
docker image history bad-nginx:1
IMAGE CREATED CREATED BY SIZE COMMENT
a469e4d1cb0c 3 years ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "ngin⊠0B
<missing> 3 years ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/sh" "-c⊠0B
<missing> 3 years ago /bin/sh -c #(nop) EXPOSE 80 0B
<missing> 3 years ago /bin/sh -c #(nop) COPY file:b0fc89bcaf962602⊠98B
<missing> 3 years ago /bin/sh -c yum install -y nginx && yum clean⊠56.7MB
<missing> 3 years ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
<missing> 3 years ago /bin/sh -c #(nop) LABEL org.label-schema.sc⊠0B
<missing> 3 years ago /bin/sh -c #(nop) ADD file:538afc0c5c964ce0d⊠215MBLa cinquiĂšme ligne, c’est sans doute la copie du fichier index.html.
La sixiĂšme ligne, c’est l' EXPOSE de l’image finale, qui est 80 (dĂ©jĂ repĂ©rĂ© dans le docker image inspect).
La septiĂšme ligne, c’est le ENTRYPOINT de l’image finale, qui est /bin/sh -c /tmp/entrypoint.sh (dĂ©jĂ repĂ©rĂ© dans le docker image inspect).
RĂ©sumons-nous… On a:
une image de base CentOS (Ă remplacer par Alpine ou Debian)
une installation de nginx.
un port 80 exposé
un ENTRYPOINT qui lance un script nginx
un fichier HTML, qu’on n’a pas encore rĂ©cupĂ©rĂ© Ăa progresse… On peut donc crĂ©er un premier đ Dockerfile
# Utiliser Alpine ou Debian comme image de base
FROM alpine
# Installer nginx sans garder l'index des paquets
RUN apk add --no-cache nginx
# Exposer le port 80
EXPOSE 80
# Ajouter un fichier HTML (remplacer /chemin/vers/votre/fichier.html par le chemin réel vers votre fichier HTML)
ADD /chemin/vers/votre/fichier.html /usr/share/nginx/html
# Définir le point d'entrée pour lancer nginx
ENTRYPOINT ["nginx", "-g", "daemon off;"]Comment récupérer ce fichu fichier HTML?
Essayons de lancer un conteneur basé sur notre image, et de récupérer le fichier HTML.
Le binaire nginx est non fonctionnel, essayons de lancer un conteneur avec un shell.
docker container run -it --name bad-2 --entrypoint=sh bad-nginx:1 sh
/usr/bin/sh: /usr/bin/sh: cannot execute binary file
Oui, je sais, il y a une faute dans la lĂ©gende du gif…
| Jean-Micheeeeeeeeeeeeeeeeel, j’ai deux mots Ă te dire! đĄ |
Le conteneur qui tourne, on oublie, donc… Jean-Michel a laissĂ© un terrain minĂ© derriĂšre lui. đŁ
Mais on a d’autres armes en rĂ©serve… Le docker container create par exempleâŻ!
docker container create --name temp_container bad-nginx:1
9149745197df413f7966181f6ca517b68713edd32ff7c35cea6e958d549a7553Utilisons maintenant docker container cp pour récupérer le fichier HTML.
Quel est le rĂ©pertoire oĂč nginx s’attend Ă trouver ses fichiers HTML Ă servir?
/usr/share/nginx/html
mkdir /tmp/html && cd /tmp/html
docker container cp temp_container:/usr/share/nginx/html .head html/index.html<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>Test Page for the Nginx HTTP Server on Red Hat Enterprise Linux</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
/*<![CDATA[*/
body {
background-color: #fff;Jean-Michel n’avait mĂȘme pas fini de remplir son fichier HTML… C’est encore le fichier de base de nginx sous CentOs. đ
Jean-Michel, t’assures pas une đ„, lĂ …
# Ce Dockerfile met en place un serveur web simple en utilisant Nginx sur Alpine Linux.
# Utilise Alpine Linux comme image de base. Alpine Linux est une distribution Linux légÚre et orientée sécurité.
FROM alpine
# Installe Nginx sans conserver l'index des paquets dans la couche.
# Nginx est un serveur web populaire qui peut Ă©galement ĂȘtre utilisĂ© comme proxy inverse, Ă©quilibreur de charge et cache HTTP.
RUN apk add --no-cache nginx
# Expose le port 80 au monde extérieur. C'est le port standard pour le trafic HTTP.
EXPOSE 80
# Ajoute un fichier HTML Ă la racine du document Nginx.
# Remplacez /chemin/vers/votre/fichier.html par le chemin réel vers votre fichier HTML.
ADD /chemin/vers/votre/fichier.html /usr/share/nginx/html
# Définit le point d'entrée pour le conteneur. Cette commande sera exécutée lorsque le conteneur démarre.
# La commande "nginx" démarre le serveur Nginx.
# Le flag "-g" nous permet de définir des directives globales. Dans ce cas, nous disons à Nginx de fonctionner au premier plan (daemon off;).
ENTRYPOINT ["nginx", "-g", "daemon off;"]Finalement, le đ de notre Dockerfile Ă©tait dĂ©jĂ bon…
Ou presque… Rien ne vous gĂȘne dans ce Dockerfile?
đ« don’t let đ«…

Savoir lancer un container avec toutes les options
-v, -p, -w, -e, --rm, etc.
Ăcrire un Dockerfile optimisĂ© pour "dockeriser" une application
pas trop gourmand, facile à modifier, paramétrable.
Ătudier une image ou un conteneur pour tout dĂ©bug Ă©ventuel.
Savoir s’outiller pour avoir des images qui servent d’environnement d’exĂ©cution Ă votre CI.
Monter un écosystÚme applicatif complexe via docker-compose.
Les TPs sont vos meilleurs amis.
La documentation officielle de Docker
