SERVEURS ET AUTOMATISATION | SESSION 4

Docker pas à pas en treize techniques

Hello World / Docker Web / Docker AppUne slide concept, une slide exemple. Quelques solutions.
On observe un concept a la fois. On reproduit. On comprend. Puis on enchaine.
Ateliers préparatoires Docker

FROM, COPY de base, RUN, cycle docker build / run / stop, notions image, conteneur, port

S1D1 - Pages HTML

COPY vers un chemin absolu dans l'image (là où le serveur web cherche le contenu) ; déclaration du port applicatif côté image avec EXPOSE et publication vers l'hôte au run avec -p hote:conteneur ; scripts du devoir (construire.sh, demarrer.sh, arreter.sh)

S1D2 - Web flat

FROM php:apache (image applicative), plusieurs COPY vers destinations différentes

S1D3 - Web data

Image avec 1 service déjà installé + RUN apt-get install du 2e service ; copier un script, le rendre exécutable, le lancer au build (RUN) ou au lancement (ENTRYPOINT) ; orchestrer plusieurs processus ; premier démarrage idempotent avec un fichier témoin (initialise.flag) pour ne pas rejouer l'init à chaque démarrage

S2D1 - Borne accueil Next.js

Build d'app JS dans le conteneur (npm install + npm run build), ARG au build, chaîne ARG -> ENV -> NEXT_PUBLIC_* ; .dockerignore et .gitignore (absolument nécessaires dès qu'on touche à Node, node_modules/ hors contexte de build et hors dépôt Git)

S2D2 - Borne médias

Socket X11 de l'hôte monté dans le conteneur (/tmp/.X11-unix, DISPLAY, xhost +local:docker)

S2D3 - Borne intelligente

Ressource externe lourde : téléchargée au build avec RUN wget (modèle léger figé dans l'image) ou au premier run dans un volume monté (modèle lourd) ; choix selon poids, fréquence et autonomie

0

Hello World - les bases du Dockerfile

Quatre paires pour comprendre la recette Docker : FROM, CMD, RUN, WORKDIR, COPY et ENV. Les briques que tout le reste reutilise.

HELLO WORLD  -  bonjourlignes minimales
FROM / CMD
NOUVELLE TECHNIQUE
FROM ... + CMD [...]

Les deux lignes minimales d'un Dockerfile.

FROM choisit l'image de base. Toujours la PREMIERE instruction.
CMD declare la commande lancee au DEMARRAGE du conteneur.
Forme JSON conseillee : CMD ["echo", "texte"] -> pas de shell
intermediaire, signaux propres a l'arret.

Sans FROM et CMD, le Dockerfile n'est pas executable.
Ce sont les tranches de pain de la sandwich, toutes les tranches vont ensuite à l'intérieur, dans l'ordre.
FROM alpine    |    CMD ["echo", "Bonjour"]
HELLO WORLD  -  bonjourEXÉCUTER une commande au lancement
FROM / CMD
Image Alpine (5 Mo). Au demarrage, le conteneur affiche un mot puis sort. C'est le hello world des images Docker.
● Dockerfileediteur
FROM alpine

CMD ["echo", "Bonjour"]
zsh - ~/dockers/bonjour
$ docker build -t bonjour .
  -> Successfully tagged bonjour:latest

$ docker run --rm bonjour
Bonjour

# Le --rm efface le conteneur
# des qu'il a fini de tourner.
Tester : changer le texte du CMD, rebuild, re-run. La sortie suit.
HELLO WORLD  -  messageEXECUTER une commande pour préparer un fichier
RUN / CMD
NOUVELLE TECHNIQUE
RUN <commande>

Executer une commande PENDANT la construction de l'image.

Chaque ligne RUN cree une nouvelle COUCHE dans l'image.
Le resultat (fichier ecrit, paquet installe, ...) est fige.

Difference cruciale :
  RUN  -> au BUILD, modifie l'IMAGE.
  CMD  -> au DEMARRAGE, lance le processus du conteneur.

On RUN apt-get install une fois ; on RUN curl le binaire au build ;
on RUN echo > /info pour deposer un fichier dans l'image.
RUN echo "depuis l'image" > /message.txt
HELLO WORLD  -  messageDeposer un fichier dans l'image au build
RUN / CMD
RUN ecrit /message.txt PENDANT le build. Au demarrage, CMD le lit. Le fichier existe deja, le run ne fait que l'afficher.
● Dockerfileediteur
FROM alpine

RUN echo "bonjour depuis l'image" > /message.txt

CMD ["cat", "/message.txt"]
zsh - ~/dockers/message
$ docker build -t message .
  => RUN echo "bonjour ..." > /message.txt
  => CACHED si pas de changement

$ docker run --rm message
bonjour depuis l'image
Le rebuild reaffiche CACHED tant que la ligne RUN n'a pas change.
HELLO WORLD  -  un fichier dans l'imageUN FICHIER dans l'IMAGE
WORKDIR / COPY
NOUVELLE TECHNIQUE
WORKDIR ... + COPY ... ...

Apporter un fichier du poste hote dans l'image.

WORKDIR cree (si besoin) et entre dans un dossier de l'image.
Toutes les instructions suivantes (RUN, COPY, CMD) partent de la.

COPY source destination apporte un fichier de l'hote vers l'image.
Source : relative au CONTEXTE de build (le . dans docker build).

Sans COPY, l'image ne contient que ce que l'image de base a livre.
WORKDIR /app    |    COPY message.txt .
HELLO WORLD  -  un fichier dans l'imagePrendre un fichier de l'hote et l'embarquer
WORKDIR / COPY
Le fichier message.txt est cree cote hote AVANT le build, puis recupere par COPY. CMD le relit au demarrage.
● Dockerfileediteur
FROM alpine

WORKDIR /app
COPY message.txt .

CMD cat message.txt
zsh - ~/dockers/fichier
$ echo 'recette du jour' > message.txt
$ docker build -t fichier .
$ docker run --rm fichier
recette du jour

# Modifier message.txt, rebuild,
# le contenu suit.
Le contexte de build (.) limite les fichiers visibles depuis le Dockerfile.
HELLO WORLD  -  espionner l'imageEspionner l'image : ls a la place de la vraie commande
CMD ls
Le conteneur n'est pas votre machine : pour savoir ce que le build a vraiment produit, on remplace temporairement la vraie commande par un ls. Rebuild en une seconde (tout vient du cache), coup d'oeil, puis on remet la vraie commande.
● Dockerfileediteur
FROM alpine

WORKDIR /app
COPY message.txt .

# CMD cat message.txt
CMD ls -la /app
zsh - ~/dockers/fichier
$ docker build -t fichier .
$ docker run --rm fichier
drwxr-xr-x  1 root  root  .
drwxr-xr-x  1 root  root  ..
-rw-r--r--  1 root  root  message.txt

# Le fichier est bien la :
# on remet CMD cat message.txt
Le ls espion repond a : qu'y a-t-il VRAIMENT dans l'image ? Une commande jetable, un rebuild d'une seconde, puis la vraie commande reprend sa place.
HELLO WORLD  -  variable d'environnementVARIABLE d'ENVIRONNEMENT
ENV / ECHO
NOUVELLE TECHNIQUE
ENV NOM=valeur

Variable d'environnement persistante, surchargeable au demarrage.

ENV declare une variable disponible au build ET au runtime.
Lisible dans un RUN, dans CMD, dans le code de l'application.

Au demarrage, --env NOM=autreValeur (ou -e NOM=...) ecrase la
valeur, sans rebuild.

Usage typique : configurer un comportement (verbosite, locale,
ou parametres d'un script) sans toucher au code.
ENV MESSAGE=monde    |    docker run -e MESSAGE=tortue ...
HELLO WORLD  -  variable d'environnementVARIABLE : Une meme image, plusieurs salutations
ENV / ECHO
ENV fixe une valeur par defaut. Au run, on peut l'ecraser. Aucun rebuild, c'est la magie : la valeur entre par la porte du run.
● Dockerfileediteur
FROM alpine

ENV MESSAGE=monde

CMD echo "Bonjour $MESSAGE !"
zsh - ~/dockers/enviro
$ docker build -t enviro .
$ docker run --rm enviro
Bonjour monde !

$ docker run --rm \
    -e MESSAGE=tortue enviro
Bonjour tortue !
L'application Python du devoir lit ses ENV via os.environ.
DOCKER APP  -  ARG injecte au frontendARGUMENT injecté avec une VARIABLE
ARG / ENV
NOUVELLE TECHNIQUE
ARG NOM  +  ENV NEXT_PUBLIC_NOM=${NOM}

Pour qu'une variable du build arrive jusqu'au code Javascript qui tourne dans le navigateur, il faut la passer par trois etapes.

ARG : visible au build, disparait apres.
ENV : survit dans l'image, lisible au runtime.
Pour Next.js : les variables prefixees NEXT_PUBLIC_ sont injectees dans
le bundle JS au moment de npm run build, donc visibles cote navigateur.

Chaine complete :
  ARG (docker build)
  -> ENV NEXT_PUBLIC_NOM (runtime Node)
  -> string en dur dans le bundle JS apres npm run build
  -> lisible dans React via process.env.NEXT_PUBLIC_NOM

Equivalent Vite : prefixe VITE_ ou config define.
Sans le prefixe NEXT_PUBLIC_ (ou VITE_ pour Vite), la variable reste cote serveur Node et n'arrive pas au navigateur.
DOCKER APP  -  ARG injecte au frontendSIGNATURE lisible au navigateur
ARG / ENV
La signature de l'etudiant est passee au build, devient une ENV au runtime, puis est injectee dans le bundle JS pour s'afficher dans la banniere de l'app Next.js.
● Dockerfile - chaine au frontendediteur
FROM node:20-slim
WORKDIR /app

ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ENV NEXT_PUBLIC_NOM=${NOM_ETUDIANT}
ENV NEXT_PUBLIC_MATRICULE=${MATRICULE}

COPY . .
RUN npm install && npm run build
CMD ["npm", "start"]
● construire.shediteur
docker build \
  --build-arg NOM_ETUDIANT="Camille" \
  --build-arg MATRICULE="AB12345" \
  -t borne-accueil:1.0 .

# Dans le code React :
# const nom = process.env.NEXT_PUBLIC_NOM;
# // -> "Camille" (en dur dans le bundle)
Sans le prefixe NEXT_PUBLIC_ (ou VITE_ pour Vite), la variable reste cote serveur Node et n'arrive pas au navigateur.
DOCKER WEB  -  FROM image applicativeIMAGE de départ
Une image applicative bien choisie supprime des dizaines de lignes de RUN et des heures de debug d'installation.
FROM
NOUVELLE TECHNIQUE
FROM ... + CMD [...]

FROM php:apache, FROM mariadb, FROM node:20 (vs FROM alpine)

Une image de Docker Hub qui contient deja l'outil principal (PHP+Apache, MariaDB, Node, Python...) au lieu de partir d'une distribution vide. On gagne du temps, on evite les erreurs d'installation. Deux familles d'images de base :

1) Images de DISTRIBUTION (alpine, debian, ubuntu) : vide, on installe tout.
   Utile pour des cas particuliers ou des combinaisons exotiques.

2) Images APPLICATIVES (php:apache, node:20, mariadb, python:3.12) :
   l'outil principal est deja installe et configure pour Docker.
   La doc officielle explique ce que l'image fournit deja.

Regle generale pour ce TP : si une image officielle existe pour votre stack, PARTEZ DE LA. On n'installe pas Apache + PHP sur Ubuntu pour les amener au meme etat que php:apache - c'est du travail deja fait.
FROM php:apache    |    CMD ["echo", "Bonjour"]
DOCKER WEB  -  FROM image applicativeIMAGE de départ - Bien la CHOISIR
FROM
Comparaison de trois Dockerfiles qui font la meme chose : servir un site PHP statique. La version applicative est radicalement plus courte.
● Vide vs Applicativeediteur
# A partir d'Ubuntu vide (long, fragile)
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
    apache2 php libapache2-mod-php
RUN a2enmod php8.1
COPY index.php /var/www/html/
EXPOSE 80
CMD ["apache2-foreground"]

# A partir de php:apache (court, robuste)
FROM php:apache
COPY index.php /var/www/html/
● Choix de base par stackediteur
Site PHP        -> FROM php:apache
Site statique   -> FROM nginx:alpine
Site Node       -> FROM node:20
App Python      -> FROM python:3.12-slim
Base de donnees -> FROM mariadb:11 ou postgres:16
App leger custom-> FROM alpine + RUN apt-get

Regle : voir hub.docker.com pour les images
        officielles de votre stack avant
        d'installer manuellement.
Une image applicative bien choisie supprime des dizaines de lignes de RUN et des heures de debug d'installation.
1

Docker Web - servir un service HTTP

Six paires pour empaqueter un site, ajouter -p et --restart, monter du contenu evolutif, orchestrer plusieurs services et alleger l'image avec un build multi-stage.

DOCKER WEB  -  serveur HTTP statiqueSERVEUR HTTP STATIQUE
EXPOSE / -p
NOUVELLE TECHNIQUE
EXPOSE <port>

Documenter le port que le service ecoute dans le conteneur.

EXPOSE est INFORMATIF. Il NE PUBLIE PAS le port vers l'hote.
La publication vers l'hote, c'est -p hote:conteneur dans docker run.

Pourquoi EXPOSE alors :
  - lisible par docker inspect (auto-documentation),
  - exploite par des outils comme docker-compose ou un proxy,
  - rappelle a l'usager quel port mapper au run.

Sans EXPOSE, on peut quand meme mapper avec -p, mais on devine.
EXPOSE 80    |    docker run -d -p 8080:80 ...
DOCKER WEB  -  serveur HTTP statiqueSERVEUR HTTP STATIQUE
EXPOSE / -p
Apache (httpd) livre par defaut une page "It works !". On la remplace par notre propre /usr/local/apache2/htdocs/index.html.
● Dockerfileediteur
FROM httpd

RUN echo "allo" > /usr/local/apache2/htdocs/index.html

EXPOSE 80
zsh - ~/dockers/web
$ docker build -t toile .
$ docker run -d \
    -p 8086:80 \
    --name toile-actif toile
$ curl http://localhost:8086
allo
$ docker stop toile-actif
$ docker rm   toile-actif
EXPOSE annonce 80 cote conteneur. -p choisit le port cote hote.
DOCKER WEB  -  contenu servi avec scriptsSCRIPTS de CONTRÔLE
scripts de pilotage
NOUVELLE TECHNIQUE
construire.sh + demarrer.sh + arreter.sh

Trois scripts bash qui remplacent les longues commandes Docker.

Chaque image livree a son triplet de scripts dans le repertoire :
  construire.sh  -> docker build -t <nom>:<version> .
  demarrer.sh    -> docker run -d -p <hote>:<conteneur> --name ...
  arreter.sh     -> docker stop ... && docker rm ...

Avantages :
  - une seule source de verite pour les ports et les noms,
  - documentation executable du projet,
  - exigence du devoir pour chaque demande.
./construire.sh   |   ./demarrer.sh   |   ./arreter.sh
DOCKER WEB  -  contenu servi avec scriptsSCRIPTS de CONTRÔLE
scripts de pilotage
Le dossier src/ contient des pages HTML. Apache les sert. Les scripts cachent les details Docker, on appelle ./demarrer.sh et c'est tout.
● Dockerfileediteur
FROM httpd
COPY src/ /usr/local/apache2/htdocs/
EXPOSE 80
● construire.shediteur
#!/bin/bash
set -e
nomImage="toile-libre"
versionImage="1.0"
docker build \
  -t "$nomImage:$versionImage" \
  -t "$nomImage:latest" .
● demarrer.shediteur
#!/bin/bash
set -e
nomImage="toile-libre"
nomConteneur="toile-libre-actif"
portHote="8087"
docker run \
  --detach \
  --name "$nomConteneur" \
  --publish "$portHote:80" \
  "$nomImage:latest"
Variante quotidienne : ./construire.sh && ./demarrer.sh && curl localhost:8087
DOCKER WEB  -  plusieurs COPY vers chemins differentsCMS flat-file en deux COPY
COPY
NOUVELLE TECHNIQUE
COPY moteur/  /var/www/html/
+  COPY contenu/  /var/www/html/contenu/

Quand l'application livree separe moteur et contenu, on les COPY a des destinations differentes dans l'image pour reconstituer la structure qu'attend le serveur.

Les fournisseurs livrent souvent un projet avec une separation claire :
  moteur/      - le code PHP (fige cote technique),
  fp-content/  - les fichiers que les editeurs peuvent deposer.

Dans l'image, le serveur web cherche tout au meme endroit
(par exemple /var/www/html/ pour php:apache).

On fait donc DEUX COPY :
  - le moteur dans /var/www/html/
  - le contenu en sous-dossier /var/www/html/contenu/

Premiere fois qu'on ecrit plusieurs COPY vers des cibles differentes.
L'image reconstitue la structure servie, peu importe comment le fournisseur a organise ses dossiers d'origine.
DOCKER WEB  -  plusieurs COPY vers chemins differentsCMS flat-file en deux COPY
COPY
Le moteur PHP est depose a la racine servie par Apache. Le contenu (articles HTML) est depose en sous-dossier visible. L'application les retrouve a l'execution.
● Dockerfile - deux COPY distinctsediteur
FROM php:8.2-apache

# Le moteur PHP a la racine servie
COPY moteur/ /var/www/html/

# Le contenu en sous-dossier visible
COPY fp-content/ /var/www/html/fp-content/

EXPOSE 80
● Structure dans l'imageediteur
/var/www/html/
  index.php           # depuis moteur/
  article.php         # depuis moteur/
  decoration/         # depuis moteur/
  fp-content/         # depuis fp-content/
    articles/
      festival.html
      pollinisateurs.html

L'image refait la structure qu'attend le moteur.
L'image reconstitue la structure servie, peu importe comment le fournisseur a organise ses dossiers d'origine.
DOCKER WEB  -  borne qui revient au rebootUne borne qui revient au reboot
--restart unless-stopped
NOUVELLE TECHNIQUE
--restart unless-stopped

Le conteneur revient automatiquement, sauf si on l'a arrete a la main.

Une borne en libre service doit survivre aux coupures de courant
et aux redemarrages de Docker. C'est le role de --restart.

Strategies disponibles :
  no                ne pas redemarrer (defaut).
  on-failure        seulement si le conteneur a sorti != 0.
  always            toujours, meme apres un docker stop.
  unless-stopped    toujours, SAUF apres un docker stop.

Pour le devoir : unless-stopped sur toutes les bornes.
docker run -d --restart unless-stopped --name ... <image>
DOCKER WEB  -  borne qui revient au rebootUne borne qui revient au reboot
--restart unless-stopped
Apres un docker run avec --restart unless-stopped, le conteneur redemarre tout seul au prochain demarrage de la machine.
● Dockerfileediteur
FROM nginx:alpine
COPY contenu/ /usr/share/nginx/html/
EXPOSE 80
zsh - poste du zoo
$ docker run \
    --detach \
    --name borne-cantons-actif \
    --publish 8080:80 \
    --restart unless-stopped \
    borne-cantons:1.0

# Apres un reboot du poste
$ docker ps
STATUS
Up 12 seconds  borne-cantons-actif
Verifier avec docker inspect : .HostConfig.RestartPolicy.Name = "unless-stopped"
DOCKER WEB  -  flag de premier demarrageUtiliser un drapeau (flag) pour détecter la 1ère exec
if / flag
NOUVELLE TECHNIQUE
if [ ! -f ".../initialise.flag" ]

Un fichier temoin qui marque que l'initialisation a deja ete faite. La prochaine fois, on detecte sa presence et on saute le bloc init.

Probleme : l'ENTRYPOINT s'execute a CHAQUE docker run / docker start.
Si l'init (creer la base, jouer le schema) y est, elle se rejoue chaque
fois. C'est inutile au mieux, destructeur au pire.

Solution : apres la premiere init, on cree un fichier temoin.
Aux demarrages suivants, le if detecte sa presence et saute le bloc init.

Pattern reutilisable pour TOUTE initialisation a faire une seule fois.
Le flag vit dans /var/lib/mysql/ : si on monte un volume sur ce dossier, le flag survit aussi.
DOCKER WEB  -  flag de premier demarrageMariaDB + Apache : init une seule fois
if / flag
Le script d'ENTRYPOINT joue le schema SQL au premier demarrage, cree initialise.flag, puis aux demarrages suivants saute directement a apache2-foreground.
● demarrer-services.shediteur
#!/bin/bash
set -e
service mariadb start
until mysqladmin ping --silent; do sleep 1; done

if [ ! -f "/var/lib/mysql/initialise.flag" ]; then
  mysql < /schema.sql
  touch /var/lib/mysql/initialise.flag
fi

exec apache2-foreground
zsh - effet sur deux demarrages
$ docker run -d --name catalogue ...
  service mariadb start
  Initialisation du schema...
  Schema initialise.
  Lancement d'Apache.

$ docker stop catalogue && docker start catalogue
  service mariadb start
  Lancement d'Apache.       # init sautee
Le flag vit dans /var/lib/mysql/ : si on monte un volume sur ce dossier, le flag survit aussi.
HELLO WORLD  -  variable d'environnementUtiliser un drapeau (flag) pour détecter la 1ère exec
ENV / ECHO
NOUVELLE TECHNIQUE
touch "$cheminTemoin"
Semaine 1 - solution-3 (Apache + MariaDB) : cheminTemoin="/var/lib/mysql/initialise.flag"

Le fichier flag, c'est un fichier qu'on a mis exprès :

if [ ! -f "$cheminTemoin" ]; then
    mysql < schema.sql
    touch "$cheminTemoin"   # <-- crée le flag
else
    # on saute l'init
fi

À quoi ça sert : éviter de réimporter le SQL (et écraser) à chaque docker run

Semaine 2 - bornes intelligentes : même concept, mais le signal n'est pas un fichier témoin, mais l'IA elle-même

Le fichier flag = c'est la présence du modèle dans le cache :

if ollama list | grep -q "qwen2.5"; then
    # modèle déjà là, on saute
else
    ollama pull qwen2.5:0.5b   # téléchargement de 400 Mo
fi

À quoi ça sert : éviter de re-télécharger 400 Mo de LLM à chaque redémarrage.

ENV MESSAGE=monde    |    docker run -e MESSAGE=tortue ...
DOCKER WEB  -  comme dans les scripts d'installationOn retire l'interactivité - comme dans les scripts
DEBIAN_FRONTEND noninteractive
NOUVELLE TECHNIQUE
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get install -y --no-install-recommends

Un script qui enleve l'interactivite

● DockerfileJED
FROM debian:12

RUN apt-get update
RUN apt-get install -y --no-install-recommends mariadb-server apache2
ENTRYPOINT ["/usr/local/bin/demarrer-services.sh"]
DOCKER WEB  -  stack multi-services dans un conteneur2 SERVICES dans le même conteneur
ENTRYPOINT
NOUVELLE TECHNIQUE
ENTRYPOINT ["/usr/local/bin/..."]

Un script qui orchestre plusieurs processus puis bascule sur le principal.

ENTRYPOINT fixe la commande qui sera TOUJOURS lancee au demarrage.
Difference avec CMD : ENTRYPOINT ne se laisse pas remplacer par les
arguments du docker run (sauf --entrypoint explicite).

Usage typique : un script bash qui prepare l'environnement (lancement
d'un service en arriere plan, attente, initialisation de la base),
puis finit par exec <processus principal>.

Le exec remplace le shell par le processus -> le conteneur reste
rattache au bon PID 1 et reagit proprement aux signaux.
ENTRYPOINT ["/usr/local/bin/demarrer-services.sh"]
DOCKER WEB  -  stack multi-services dans un conteneurMariaDB + Apache dans un seul conteneur
2 SERVICES
Le script ENTRYPOINT lance MariaDB, attend qu'elle reponde, initialise au premier demarrage, puis bascule sur Apache en avant-plan.
● Dockerfileediteur
FROM debian:12
RUN apt-get update && apt-get install -y mariadb-server apache2
COPY demarrer-services.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/demarrer-services.sh
EXPOSE 80
ENTRYPOINT ["/usr/local/bin/demarrer-services.sh"]
● demarrer-services.shediteur
#!/bin/bash
set -e
service mariadb start
until mysqladmin ping --silent; do
  sleep 1
done
if [ ! -f /var/lib/mysql/init.flag ]; then
  mysql < /entree/schema.sql
  touch /var/lib/mysql/init.flag
fi
# Bascule sur le principal
exec apache2-foreground
Sans le exec final, le conteneur sortirait des qu'Apache se termine. La cle : exec.
2

Docker App - app avec deps et donnees

Des paires pour passer du hello world a une vraie application : dockerignore obligatoire, ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.

DOCKER WEB  -  .dockerignoreIgnorer les fichiers qui se regénèrent
dockerignore et nodes_modules
NOUVELLE TECHNIQUE
fichier .dockerignore

Un fichier voisin du Dockerfile, cousin du .gitignore, qui dit a Docker ce qu'il ne faut PAS envoyer au build

Au docker build, Docker envoie tout le dossier courant au demon qui
build le conteneur.
Sans .dockerignore : .git/, node_modules/, donnees-bd/ et cie partent aussi.
=> Resultat : build lent, image potentiellement polluee, secrets exposes.

Critique des qu'on a un projet Node (node_modules pese des centaines
de Mo). Ce dossier est complètement regénéré par le npm build, alors il
doit être listé dans le .dockerignore et aussi dans le .gitignore.

Un projet avec donnees locales (donnees-bd/, sons/, caches) servies sur
un volume ne doit pas les copier aussi dans l'image.

Symetrique au .gitignore : meme philosophie, autre destinataire.
Reflexe : creer le .dockerignore en meme temps que le Dockerfile.
DOCKER APP  -  .dockerignoreServir un dossier statique avec scripts
dockerignore et nodes_modules
Le .dockerignore evite d'envoyer les scripts d'usage, le README et tout dossier de donnees locales au demon Docker pendant le build.
● .dockerignoreediteur
.git
*.log
node_modules
donnees-bd
construire.sh
demarrer.sh
arreter.sh
README.md
zsh - effet du .dockerignore
$ docker build -t toile-libre .
  Sending build context to Docker daemon
  -> 12 kB    # au lieu de 350 Mo sans .dockerignore

# Sans le fichier, la meme commande envoie
# node_modules/, .git/, donnees-bd/ : enorme,
# inutile, et a chaque rebuild.
Reflexe : creer le .dockerignore en meme temps que le Dockerfile.
DOCKER APP  -  app Python avec depsOrdonner les couches
LAYERS
NOUVELLE TECHNIQUE
Mettre les couches STABLES en haut,
les VOLATILES en bas.

Mettre les couches STABLES en haut, les VOLATILES en bas.

Docker met chaque couche en cache. Une couche est invalidee si le
fichier qu'elle copie change, OU si la ligne change.

Une fois invalidee, toutes les couches suivantes le sont aussi.

Strategie : COPY requirements.txt puis RUN pip install AVANT le COPY .
du code. Resultat :
  - modifier app.py -> rebuild instantane.
  - ajouter une dependance -> rebuild seulement de pip + suite.
COPY requis.txt . | RUN pip install -r requis.txt | COPY . .
BORNE ACCUEIL  -  Next.js en une seule etapeOrdonner les couches
LAYERS
NOUVELLE TECHNIQUE
FROM node:20  +  npm install  +  npm run build  +  npm start

L'approche d'initiation pour la borne d'accueil Next.js : un Dockerfile classique en une etape qui contient TOUT (Node, sources, node_modules, build). Lisible, simple, chaque ligne se comprend. Pour cette étape : un seul FROM, un seul flux de commandes.

C'est ACCEPTABLE pour ce labo : la priorite est la comprehension.

Ordre des couches pour profiter du cache de build :
  1) COPY package*.json (fichiers stables)
  2) RUN npm install (lent mais reste en cache si package*.json ne change pas)
  3) COPY . . (le code, qui change souvent)
  4) RUN npm run build

Une modification du code -> rebuild rapide (npm install reste en cache).

L'image finale est plus grosse (~300 a 500 Mo) parce qu'elle embarque
Node, npm, les sources et tout le contenu de node_modules.
Multi-stage : pour aller plus loin, on peut couper Node de l'image finale (slide DOCKER WEB image finale legere). Le multi-stage est presente plus tard comme amelioration facultative.
BORNE ACCUEIL  -  Next.js en une seule etapeBorne d'accueil Next.js, version simple
S2-D1
Le Dockerfile complet pour S2-D1 sans multi-stage. La borne demarre sur le port 3000 du conteneur, qu'on publie sur 8090 cote hote.
● Dockerfile - borne accueilediteur
FROM node:20
WORKDIR /app

ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ENV NEXT_PUBLIC_NOM=${NOM_ETUDIANT}
ENV NEXT_PUBLIC_MATRICULE=${MATRICULE}

COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

EXPOSE 3000
CMD ["npm", "start"]
● demarrer.shediteur
#!/bin/bash
set -e
docker run -d \
  --name borne-accueil \
  -p 8090:3000 \
  --restart unless-stopped \
  borne-accueil:1.0

# Le visiteur ouvre http://localhost:8090
Multi-stage : pour aller plus loin, on coupera Node de l'image finale (slide DOCKER WEB image finale legere).
BORNE MEDIAS  -  choisir parmi 3 stacksLe docker run de chaque option
options
Ce qui change entre les trois : presence de port, montages de socket, audio. Le reste (image, scripts, .dockerignore) suit le meme schema.
● Dockerfile - 3 patterns differentsediteur
# Pygame
FROM python:3.12-slim
RUN apt-get update && \
    apt-get install -y libsdl2-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]

# three.js
FROM node:20
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npx", "serve", "dist"]
demarrer.sh - les 3 cas
# Pygame (X11, pas de port)
xhost +local:docker
docker run --rm --name quiz \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  borne-quiz:1.0

# Qt = idem + audio Pulse
  -v /run/user/$(id -u)/pulse/native:/run/...

# three.js (port HTTP, pas de X11)
docker run -d --name jeu3d -p 8091:3000 \
  borne-jeu3d:1.0
Schema commun. Differences : port pour three.js, X11 pour Pygame/Qt, audio Pulse en plus pour Qt.
DOCKER APP  -  app desktop avec X11FORWARDER X avec un VOLUME pour AFFICHER
-e DISPLAY
NOUVELLE TECHNIQUE
-v /tmp/.X11-unix:/tmp/.X11-unix  +  -e DISPLAY

Pour les apps qui ne sont pas des serveurs web (Pygame, Qt) mais doivent dessiner une fenetre sur l'ecran de l'hote. On monte le socket X11.

Pas de port reseau a publier : c'est une app desktop, pas un serveur HTTP.

Le serveur d'affichage X11 de l'hote ecoute sur un socket Unix.
On monte ce socket dans le conteneur (volume bind tres specifique).
Le programme dans le conteneur lit DISPLAY et parle au socket monte.

xhost +local:docker autorise les connexions locales avant le run.
xhost -local:docker apres pour refermer la securite.

Meme principe pour le son : monter le socket PulseAudio.
Pas de -p ici. Une app desktop dans un conteneur n'expose pas de port reseau.
DOCKER APP  -  app desktop avec X11Borne Pygame qui affiche un quiz
-e DISPLAY
L'app Pygame s'execute dans le conteneur mais sa fenetre s'affiche sur le bureau de l'hote, via le socket X11 monte.
● Dockerfile - app desktopediteur
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      libsdl2-2.0-0 libsdl2-image-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]
● demarrer.shediteur
#!/bin/bash
set -e
xhost +local:docker
docker run --rm \
  --name borne-quiz \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  borne-quiz:1.0
xhost -local:docker
Pas de -p ici. Une app desktop dans un conteneur n'expose pas de port reseau.
BORNE MEDIAS  -  choisir parmi 3 stacksPygame | Qt | three.js
APP
NOUVELLE TECHNIQUE
Differences : port pour three.js, X11 pour Pygame/Qt,
audio Pulse en plus pour Qt.

3 stacks, meme travail Docker

Pygameapp Python + SDL2. PAS de port reseau, X11 oui, audio nonQuiz a choix multiples sur les especes. Stack la plus simple : pip install pygame + CMD python quiz.py
Qtapp Python + Qt6 (PySide6). PAS de port, X11 oui, audio PulseAjoute le montage du socket Pulse pour la lecture des sons. Generer-sons.py s'execute pendant le build (assets synthetises).
three.jsapp web Vite/JS. PORT HTTP oui, X11 non, pas d'audio specifique.Build dans l'image : npm install + npm run build
  • Ce qui change : ports, X11, audio.
  • Trois experiences tres differentes, mais l'image Docker se construit de la meme facon : choisir une base, installer les deps, copier le code, definir CMD.
BORNE MEDIAS  -  choisir parmi 3 stacksLe docker run de chaque option
options
Ce qui change entre les trois : presence de port, montages de socket, audio. Le reste (image, scripts, .dockerignore) suit le meme schema.
● Dockerfile - 3 patterns differentsediteur
# Pygame
FROM python:3.12-slim
RUN apt-get update && \
    apt-get install -y libsdl2-2.0-0
RUN pip install pygame
COPY quiz.py .
CMD ["python", "quiz.py"]

# three.js
FROM node:20
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["npx", "serve", "dist"]
demarrer.sh - les 3 cas
# Pygame (X11, pas de port)
xhost +local:docker
docker run --rm --name quiz \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  borne-quiz:1.0

# Qt = idem + audio Pulse
  -v /run/user/$(id -u)/pulse/native:/run/...

# three.js + Electron : meme schema que Pygame/Qt
# (apps desktop kiosque, pas de port reseau)
xhost +local:docker
docker run --rm --name jeu3d \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  borne-jeu3d:1.0
Schema commun. Differences : port pour three.js, X11 pour Pygame/Qt, audio Pulse en plus pour Qt.
DOCKER APP  -  ressource au build vs au runtimeDonnées externes ou internes
wget / -v cache
NOUVELLE TECHNIQUE
RUN wget ... (au build)  vs  -v cache:/chemin

Pour un modele IA, un binaire, des donnees lourdes : on choisit consciemment si elles vivent DANS l'image (au build) ou DANS un volume (au runtime).

Pattern A - au build avec RUN wget :
+ image autonome, fonctionne offline immediatement,
- image plus grosse, rebuild = re-telechargement.
Bon pour : modeles legers (~10 a 50 Mo), donnees stables.

Pattern B - au premier run dans un volume monte :
+ image legere, telechargement une seule fois entre conteneurs,
- depend du reseau au premier demarrage.
Bon pour : modeles lourds (~ centaines de Mo), donnees evolutives.

Criteres : poids, frequence de mise a jour, autonomie hors-ligne.
Volume = pas dans l'image. RUN wget = dans l'image. Decision selon poids et frequence de mise a jour.
DOCKER APP  -  ressource au build vs au runtimeDeux strategies, meme genre d'app
wget / -v cache
A gauche, un modele leger ONNX (14 Mo) embarque dans l'image au build : autonomie totale. A droite, ollama charge un LLM (400 Mo) au premier run dans un volume nomme partage.
● Pattern A - au build (ONNX 14 Mo)editeur
FROM python:3.12-slim
RUN apt-get update && \
    apt-get install -y wget
RUN wget -O /modele.onnx \
    https://...mobilenet.onnx
RUN wget -O /labels.txt \
    https://...labels.txt
RUN pip install onnxruntime
COPY vision.py /
CMD ["python", "/vision.py"]
● Pattern B - au runtime (LLM 400 Mo)editeur
FROM python:3.12-slim
RUN curl -fsSL https://ollama.com/install.sh | sh
COPY entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]

# au run :
$ docker run -d \
    -v zoo-ollama-cache:/root/.ollama \
    borne-llm:1.0
Volume = pas dans l'image. RUN wget = dans l'image. Decision selon poids et frequence de mise a jour.

Et maintenant ?

  1. Reproduisez chaque paire chez vous, dans l'ordre des trois categories.
  2. Pour chaque image a construire, demandez : c'est du web ou de l'app ?
  3. Ecrivez le Dockerfile dans VOTRE contexte, pas a partir d'un modele.
  4. Si vous bloquez, revenez a la slide concept correspondante.
  5. Reservez Internet pour les details, pas pour les solutions toutes faites.
Comprendre une instruction Docker, c'est savoir quand on NE l'utilise PAS.
DOCKER APP  -  app Python avec depsUne app Python qui beneficie du cache
docker rm
Un projet reel : Python 3.12, une dependance (requests), un app.py. Le cache de pip install reste valide tant que requis.txt n'a pas bouge.
● Dockerfileediteur
FROM python:3.12-slim
WORKDIR /app

COPY requis.txt .
RUN pip install -r requis.txt

COPY . .
CMD ["python", "app.py"]
zsh - ~/dockers/python
$ docker build -t cite-python .
  # 1er build : 20 s (pip install)

# Modifier app.py, rebuild :
$ docker build -t cite-python .
  # 2e build : 1 s (cache pip)

$ docker run --rm cite-python
Bonjour monde ! ...
Inverser COPY . AVANT pip install -> rebuild lent a chaque modif.
B

BONUS VOLUME

Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.

DOCKER APP  -  le monde de l'executionLes trois types de montages
mount
Quand un conteneur tourne (docker run, Compose), on peut lui attacher trois types de montages : « mount » est le terme parapluie qui les couvre tous les trois.
volume (nomme ou anonyme)donnees dans /var/lib/docker/volumes/gere par Docker
bind mountun repertoire de l'hote prete au conteneurgere par vous
tmpfsde la RAM, disparait a l'arret du conteneurgere par le noyau
  • Donc techniquement, un volume EST un type de mount.
  • Le drapeau moderne --mount type=volume|bind|tmpfs force a nommer le type explicitement.
volume | bind mount | tmpfs : trois montages, un seul parapluie.
DOCKER APP  -  cache lourd entre les runsModele LLM telecharge une fois, reutilise toujours
VOLUME
NOUVELLE TECHNIQUE
docker volume create  +  --volume <nom>:<cible>

Un cache pilote par Docker, partage entre runs et conteneurs.

Different d'un bind mount : ici Docker gere le dossier, pas l'hote.
Avantages :
  - portable (pas de chemin hote a hardcoder),
  - persiste apres un docker rm du conteneur,
  - partageable entre plusieurs conteneurs.

Usage typique : un modele IA, des donnees de base, un cache npm.
On peut creer le volume a l'avance (docker volume create) ou laisser
Docker le creer a la volee a la premiere utilisation.
docker volume create cache-modeles   +   --volume cache-modeles:/data
DOCKER APP  -  cache lourd entre les runsModele LLM telecharge une fois, reutilise toujours
VOLUME
Le modele pese 400 Mo. On le met dans un volume nomme : la borne le tire au premier lancement, les lancements suivants demarrent en quelques secondes.
● Dockerfileediteur
FROM ollama/ollama
VOLUME /root/.ollama
EXPOSE 11434
zsh - poste du zoo
# Creer le volume une fois
$ docker volume create cache-modeles

# Premier run : telechargement (~3 min)
$ docker run --detach \
    --name borne-narrative-actif \
    --volume cache-modeles:/root/.ollama \
    borne-narrative:1.0

# Run suivant : instantane
$ docker stop borne-narrative-actif
$ docker rm   borne-narrative-actif
$ docker run --detach \
    --name borne-narrative-actif \
    --volume cache-modeles:/root/.ollama \
    borne-narrative:1.0
Lister : docker volume ls. Inspecter : docker volume inspect cache-modeles.
O

OPTIMISATION

Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.

DOCKER WEB  -  image finale legereMulti-stage pour une app Next.js
COPY --from
NOUVELLE TECHNIQUE
FROM ... AS <nom>  +  COPY --from=<nom>

Deux etapes dans un meme Dockerfile : on construit avec l'une, on sert avec l'autre.

FROM ... AS <nom> declare une etape de build nommee. Cette etape
installe les outils lourds (Node, compilateurs, ...) et produit des
fichiers (un dossier dist/, un binaire compile).

Ensuite : un second FROM (sans AS) demarre une image FINALE legere.
COPY --from=<nom> pioche les fichiers utiles dans l'etape precedente.

Resultat : l'image finale ne contient PAS les outils de build.
Image Next.js : ~150 Mo au lieu de 1.2 Go.
FROM node:20 AS atelier  +  FROM node:20-slim  +  COPY --from=atelier ...
DOCKER WEB  -  image finale legereMulti-stage pour une app Next.js
COPY --from
Etape 1 : Node complet pour npm install et npm run build. Etape 2 : Node slim qui ne sert que le rendu produit.
● Dockerfileediteur
FROM node:20-slim AS atelier
WORKDIR /generation
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM node:20-slim
WORKDIR /borne
COPY --from=atelier /generation ./
EXPOSE 3000
CMD ["npm", "start"]
L'etape "atelier" disparait du livrable final. Seule l'image finale est poussee.
DOCKER WEB  -  persistance simpleDemontrer la persistance sans volume
persistance
NOUVELLE TECHNIQUE
docker stop  +  docker start  (sans rm)

Tant qu'on ne fait pas docker rm, les donnees ecrites dans le conteneur survivent aux arrets et redemarrages. Pas besoin de volume pour ce cas.

Idee fausse repandue : il faudrait un volume pour la persistance.
En realite, les donnees ecrites dans le systeme de fichiers du conteneur
vivent dans sa derniere couche et survivent a stop/start du meme conteneur.

On perd les donnees seulement si on fait docker rm (destruction du conteneur).

Demarche pedagogique :
  1) Version simple : sans volume, persistance via stop/start.
  2) Version avancee : avec volume, ca survit aussi a rm/run.
Sans -v, les donnees survivent a stop/start. Avec -v, elles survivent aussi a rm/run.
DOCKER WEB  -  persistance simpleDemontrer la persistance sans volume
persistance
On ajoute une espece dans la base, on stop, on start, l'espece est encore la. Seule la destruction du conteneur (docker rm) ferait perdre les donnees.
● Sequence dans le terminalediteur
$ docker run -d --name catalogue -p 8080:80 catalogue:1.0
$ docker exec catalogue mysql -e "INSERT INTO espece ..."
# Page web : 13 especes

$ docker stop catalogue
$ docker start catalogue
# Page web : 13 especes (toujours la)

$ docker stop catalogue && docker rm catalogue
$ docker run -d --name catalogue ...
# Page web : 12 especes (perdue)
● Le piege du rmediteur
$ docker rm catalogue
  catalogue
  -> les donnees ecrites dans la couche du conteneur
     viennent d'etre detruites avec lui.

Pour qu'elles survivent au rm aussi : monter un volume
(slide suivante)
Sans -v, les donnees survivent a stop/start. Avec -v, elles survivent aussi a rm/run.
DOCKER WEB  -  contenu evolutif sans rebuildUn CMS dont le contenu evolue sans rebuild
--volume read-only
NOUVELLE TECHNIQUE
--volume <hote>:<cible>:ro

Monter un dossier de l'hote dans le conteneur en lecture seule.

Le bind mount expose un dossier de l'hote a un endroit du conteneur.
Different d'un volume nomme : ici c'est l'HOTE qui pilote le contenu.
Le suffixe :ro (read-only) interdit au conteneur d'ecrire dedans.

Utile pour :
  - du contenu evolutif qu'on met a jour SANS rebuild,
  - de la securite (le conteneur ne peut pas corrompre le dossier),
  - du partage entre plusieurs conteneurs lecteurs.

BONUS pour le docker web no 2
docker run ... -v $(pwd)/contenu:/var/www/html/articles:ro ...
DOCKER WEB  -  contenu evolutif sans rebuildUn CMS dont le contenu evolue sans rebuild
--volume read-only
Le moteur PHP est dans l'image. Le contenu (articles HTML) vient de l'hote, monte en lecture seule. Ajouter un fichier suffit.
● Dockerfileediteur
FROM php:8.2-apache
COPY index.php /var/www/html/
EXPOSE 80
zsh - poste du studio
# Premier demarrage
$ docker run \
    --detach \
    --name moteur-festival-actif \
    --publish 8081:80 \
    --volume "$(pwd)/articles:/var/www/html/articles:ro" \
    moteur-festival:1.0

# Ajouter un article cote hote
$ cp nouveau-fest.html articles/
$ curl http://localhost:8081
  -> le nouveau article apparait
     SANS rebuild de l'image.
Retirer :ro pour autoriser l'ecriture. Reflexe : commencer par :ro. | BONUS : attention, ce n'est pas un vrai docker réutilisable.
DOCKER WEB  -  stack multi-services dans un conteneurSCRIPT dans le conteneur
COPY
NOUVELLE TECHNIQUE
COPY install-database.sh .

Un script qui initialise

apt-get update
echo "ca marche"
apt-get install -y --no-install-recommends mariadb-server mariadb-client
ENTRYPOINT ["/usr/local/bin/demarrer-services.sh"]
2

SIGNATURE

Trois paires pour passer du hello world a une vraie application : ordre des couches pour le cache, parametres injectes au build, et donnees lourdes hors de l'image, dans un volume.

DOCKER APP  -  signature au buildBanniere injectee au build
ARG / --build-arg
NOUVELLE TECHNIQUE
ARG <nom>=<defaut>  +  --build-arg <nom>=<valeur>

Une variable qui n'existe QUE pendant le build.

ARG est lisible pendant docker build, puis disparait.
Au build, --build-arg NOM=valeur ecrase la valeur par defaut.

Pour qu'une valeur ARG survive dans l'image au runtime, on la
RECOPIE dans une ENV :
    ARG NOM_ETUDIANT=anonyme
    ENV NOM_ETUDIANT=${NOM_ETUDIANT}

Cas d'usage du devoir :
  - banniere d'identification (nom, matricule, date de build),
  - choix d'une variante au build sans toucher au Dockerfile.
ARG NOM_ETUDIANT=anonyme   +   docker build --build-arg NOM_ETUDIANT=Camille
DOCKER APP  -  signature au buildBanniere injectee au build
ARG / --build-arg
L'image porte une signature visible (nom, matricule, date). Deux images construites le meme jour par deux etudiants ne sont pas identiques.
● Dockerfileediteur
FROM httpd
ARG NOM_ETUDIANT=anonyme
ARG MATRICULE=000000
ARG BUILD_DATE=inconnu
ENV NOM_ETUDIANT=${NOM_ETUDIANT}
ENV MATRICULE=${MATRICULE}
ENV BUILD_DATE=${BUILD_DATE}
COPY signature.html /usr/local/apache2/htdocs/
EXPOSE 80
zsh - construire.sh
docker build \
  --build-arg NOM_ETUDIANT="Camille" \
  --build-arg MATRICULE="AB12345" \
  --build-arg BUILD_DATE="$(date -I)" \
  -t borne-cetaces:1.0 .

# Verifier
$ docker inspect borne-cetaces:1.0 \
    --format '{{ .Config.Env }}'