02 Guide pas-a-pas - Atelier 2 - Semaine 2

Dockerisez la borne
medias interactive

Trois options de stack au choix : Pygame (app native), Qt (app native + audio) ou three.js (frontend web). Le travail Docker est equivalent, l'interet pedagogique est de comparer ce qui change selon le toolkit.

Ce qu'on vous fournit

Le dossier labos/semaine-2/demande-2-borne-medias/ contient trois sous-dossiers - vous en choisissez un seul :

Vous y ajoutez : Dockerfile, .dockerignore, construire.sh, demarrer.sh, arreter.sh, votre README.md.

Avant de commencer

Pre-requis et choix d'option

Quelle option choisir ?

Option Pygame - Quiz interactif

Niveau : intermediaire - Toolkit : Python + SDL2 - Image finale : ~250 Mo

L'option la plus simple a dockeriser. Un seul stage, dependances Python pures. Bon choix si vous voulez vous concentrer sur les fondamentaux Docker (ARG, ENV, X11) sans bagarrer avec un toolkit complexe.

Option Qt - Encyclopedie sonore

Niveau : avance - Toolkit : Python + Qt6 + GStreamer + PulseAudio - Image finale : ~700 Mo

L'option la plus enrichissante techniquement : vous touchez a la lecture audio dans Docker (montage du socket PulseAudio), aux librairies systeme XCB de Qt, et a la generation d'artefacts pendant le build (sons synthetises). Bon choix si vous etes a l'aise avec Linux.

Option three.js - Jeu 3D WebGL

Niveau : intermediaire - Toolkit : three.js + Vite + nginx - Image finale : ~30 Mo

L'option qui illustre le mieux le multi-stage : etape Node pour le build, etape nginx pour le run. Image finale legere. Bon choix si vous voulez voir un cas reel de pipeline frontend en Docker.

Vue d'ensemble

Le parcours en sept etapes

Chaque etape detaille d'abord le tronc commun, puis les specificites par option (encadrees en blocs colores).

Etape 01

Inspecter le materiel et choisir l'option

Etape 02

Comprendre l'architecture cible

Etape 03

Ecrire le Dockerfile

Etape 04

Injecter la banniere ARG -> ENV

Etape 05

Construire et demarrer

Etape 06

Encapsuler dans 3 scripts

Etape 07

Finaliser README et .dockerignore

01

Inspecter le materiel et choisir votre option

Lire le code source de l'option choisie. Vous devez savoir ce qu'elle fait avant de l'emballer. Le code est court (200 a 500 lignes selon l'option).

Indications par option

PYGAME

Lisez option-pygame/application/quiz.py. Reperez la boucle Pygame (events / update / render), la machine a etats (accueil, question, resultat), le tableau des questions (4 ecosystemes x 2 ou 3 questions). Notez ou est lue la banniere : os.environ.get("NOM_ETUDIANT", ...).

QT

Lisez option-qt/application/encyclopedie.py. Reperez la classe FenetreEncyclopedie (QMainWindow), la liste a gauche (QListWidget), la fiche a droite (QLabel + QPushButton), le lecteur audio (QMediaPlayer + QAudioOutput). Lisez aussi generer-sons.py qui sera execute pendant le build.

THREEJS

Lisez option-threejs/src/main.js. Reperez la creation de la scene (THREE.Scene, PerspectiveCamera), le groupe de belugas, l'animation des vagues, le raycaster pour le clic. Lisez aussi vite.config.js pour comprendre le mecanisme define qui injecte la banniere dans le bundle.

Vous savez decrire l'experience visiteur en 3 phrases. Vous savez ou la banniere est lue dans le code. Vous avez identifie les dependances principales.
02

Comprendre l'architecture Docker cible

Avant de coder le Dockerfile, comprenez ce qu'il doit produire. Les contraintes ne sont pas les memes selon l'option.

PYGAME

Stack visee

python:3.12-slim + pygame==2.5.2 + fonts-dejavu-core.

Communication avec l'hote

  • X11 : monter /tmp/.X11-unix, lire $DISPLAY
  • Aucun port reseau (c'est une app, pas un serveur)
QT

Stack visee

python:3.12-slim + PySide6==6.7.2 + libxcb* + gstreamer* + fonts-dejavu-core.

Communication avec l'hote

  • X11 : monter /tmp/.X11-unix, lire $DISPLAY
  • Audio : monter le socket PulseAudio /run/user/$(id -u)/pulse/native et exposer PULSE_SERVER
  • Aucun port reseau
THREEJS

Stack visee

Etape 1 : node:20-slim avec three + vite. Etape 2 : nginx:1.27-alpine.

Communication avec l'hote

  • Aucun X11
  • Aucun audio
  • Port HTTP : 80 cote container, 8091 cote hote
Vous savez nommer l'image de base, les dependances systeme principales, et comment le container communique avec l'hote pour votre option.
03

Ecrire le Dockerfile

Creer le fichier Dockerfile a la racine de votre dossier d'option (a cote de application/ ou des sources).

Squelette - option Pygame

Dockerfile - Pygame
FROM python:3.12-slim
LABEL borne="medias-pygame"

# Polices systeme pour le rendu texte SDL2
RUN apt-get update && apt-get install -y \
    fonts-dejavu-core \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir pygame==2.5.2

WORKDIR /borne
COPY application/ /borne/

CMD ["python", "[fichier-quiz]"]

Squelette - option Qt

Dockerfile - Qt
FROM python:3.12-slim
LABEL borne="medias-qt"

# Couche XCB pour Qt + GStreamer pour l'audio + polices
RUN apt-get update && apt-get install -y \
    libxcb-xinerama0 libxcb-cursor0 libxcb-icccm4 libxcb-image0 \
    libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 \
    libxcb-sync1 libxcb-xfixes0 libxcb-xkb1 libxkbcommon-x11-0 \
    libgl1 libegl1 libfontconfig1 libdbus-1-3 \
    gstreamer1.0-plugins-good gstreamer1.0-plugins-base \
    gstreamer1.0-pulseaudio \
    fonts-dejavu-core \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir PySide6==6.7.2

WORKDIR /borne
COPY application/ /borne/

# Generer les .wav AU BUILD pour qu'ils soient empaquetes
RUN python /borne/generer-sons.py /borne/sons

CMD ["python", "[fichier-encyclopedie]"]

Squelette - option three.js

Dockerfile - three.js (multi-stage)
# --- Etape 1 : build du bundle ---
FROM node:20-slim AS builder
WORKDIR /app

# Cache : package.json AVANT le code source
COPY package*.json ./
RUN npm ci

COPY . ./
RUN npm run build

# --- Etape 2 : serve avec nginx ---
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
Pour Pygame et Qt : ne lancez pas votre application avec un ENTRYPOINT en mode shell. Utilisez la forme exec (CMD ["python", "fichier.py"]). Sinon les signaux ne sont pas transmis et docker stop attend 10 secondes avant de tuer.
Pour Qt : si vous oubliez les libxcb-*, l'application demarre puis affiche qt.qpa.plugin: Could not load the Qt platform plugin "xcb" et plante. C'est l'erreur classique. Le Dockerfile fourni en exemple liste le minimum necessaire.
Pour three.js : si vous oubliez le .dockerignore, votre node_modules/ local est envoye au daemon a chaque build et peut peser plusieurs centaines de Mo. Voir etape 7.
Le fichier Dockerfile existe, contient les bonnes instructions pour votre option, et la prochaine etape (le build) marche.
04

Injecter la banniere d'identification

Faire apparaitre votre nom, votre matricule et la date de build en haut de l'experience. La banniere est ce qui distingue votre image de celle de votre voisin.

PYGAME

Chaine ARG -> ENV -> Python

Dans le Dockerfile :

ARG NOM_ETUDIANT
ARG MATRICULE
ARG BUILD_DATE
ENV NOM_ETUDIANT=$NOM_ETUDIANT
ENV MATRICULE=$MATRICULE
ENV BUILD_DATE=$BUILD_DATE

Dans quiz.py, la lecture est deja faite : os.environ.get("NOM_ETUDIANT", "Inconnu"). Verifiez la zone d'affichage dans la fonction qui dessine la banniere.

QT

Chaine identique a Pygame

Memes ARG + ENV dans le Dockerfile. Dans encyclopedie.py, lecture par os.environ.get(...), affichage dans le QLabel de banniere en haut de la fenetre.

THREEJS

Chaine ARG -> vite.config.define -> bundle

Particularite : pas de process.env au runtime dans un bundle nginx. Vite remplace les identifiants au build via define.

Dans le Dockerfile :

ARG NOM_ETUDIANT
ARG MATRICULE
ARG BUILD_DATE
ENV NOM_ETUDIANT=$NOM_ETUDIANT
ENV MATRICULE=$MATRICULE
ENV BUILD_DATE=$BUILD_DATE
RUN npm run build

Le vite.config.js fourni lit process.env.NOM_ETUDIANT au moment du build et fait define: { __NOM_ETUDIANT__: JSON.stringify(...) }. Dans main.js, on utilise simplement __NOM_ETUDIANT__ comme identifiant litteral.

Pour les trois options : si vous ne passez pas --build-arg au moment du docker build, l'ARG reste a sa valeur par defaut. Vous verrez "Etudiant Inconnu" dans la banniere. Ce n'est pas un bug, c'est que vous n'avez pas appele construire.sh avec vos arguments.
Pour three.js : si vous mettez ENV NOM_ETUDIANT=... APRES RUN npm run build, votre bundle est fige sans la valeur. L'ordre dans le Dockerfile compte. La regle generale : tout ce qui doit etre lu pendant le build doit etre defini avant.
Apres rebuild, vous voyez votre nom, votre matricule et la date dans la banniere de la borne, peu importe l'option choisie.
05

Construire et demarrer

Build de l'image avec les bons --build-arg, demarrage avec les bonnes options de runtime.

Build (commun aux trois options)

Terminal
$ docker build \
    --build-arg NOM_ETUDIANT="Prenom Nom" \
    --build-arg MATRICULE="1234567" \
    --build-arg BUILD_DATE="$(date -I)" \
    -t zoo-borne-medias-[option]:1.0 .

Demarrage par option

PYGAME
xhost +local:docker
docker run -d \
  --name borne-pygame \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  zoo-borne-medias-pygame:1.0

Une fenetre Pygame doit s'ouvrir sur votre bureau dans les 2 secondes.

QT
xhost +local:docker
docker run -d \
  --name borne-qt \
  -e DISPLAY=$DISPLAY \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -v /run/user/$(id -u)/pulse/native:/run/pulse/native \
  -e PULSE_SERVER=unix:/run/pulse/native \
  zoo-borne-medias-qt:1.0

Une fenetre Qt s'ouvre, vous pouvez cliquer "Ecouter le son" sur une espece et entendre le son synthetise.

THREEJS
docker run -d \
  --name borne-threejs \
  -p 8091:80 \
  zoo-borne-medias-threejs:1.0

Ouvrez http://localhost:8091/. La scene 3D s'affiche, les belugas sont cliquables.

Pygame / Qt : "cannot connect to X server" -> vous n'avez pas fait xhost +local:docker. Ou $DISPLAY est vide (vous etes en SSH sans forwarding X). Verifiez avec echo $DISPLAY.
Qt : son inaudible -> le socket PulseAudio n'est pas monte ou le chemin /run/user/$(id -u)/pulse/native n'existe pas (PulseAudio pas installe ou utilisateur sans session graphique). Verifiez ls -la /run/user/$(id -u)/pulse/native sur l'hote.
three.js : "ERR_EMPTY_RESPONSE" -> nginx tourne mais le bundle n'a pas ete copie. Verifiez docker logs borne-threejs. Souvent c'est un mauvais chemin dans COPY --from=builder /app/dist (peut-etre /app/build ou /app/out selon la config Vite).
L'experience visiteur fonctionne entierement : navigation, interaction, votre banniere visible. docker logs ne montre aucune erreur Python ou nginx.
06

Encapsuler dans trois scripts d'usage

Trois scripts en kebab-case, executables. construire.sh accepte deux arguments optionnels (nom, matricule) et calcule la date.

construire.sh - structure commune

construire.sh
#!/bin/bash
set -e

nomEtudiant="${1:-Etudiant Inconnu}"
matricule="${2:-0000000}"
dateBuild="$(date -I)"
nomImage="zoo-borne-medias-[option]:1.0"

echo "[info] Build de $nomImage avec $nomEtudiant ($matricule)"
docker build \
    --build-arg NOM_ETUDIANT="$nomEtudiant" \
    --build-arg MATRICULE="$matricule" \
    --build-arg BUILD_DATE="$dateBuild" \
    -t "$nomImage" .
echo "[ok] Image $nomImage prete."

demarrer.sh - varie selon l'option

PYGAME / QT

Ajoutez xhost +local:docker en debut de script (sinon "cannot connect to X server"). Variables : nomImage, nomContainer. Pour Qt, montez aussi PulseAudio.

THREEJS

Variables : nomImage, nomContainer, portHote (8091), portContainer (80). Pas d'X11, pas d'audio. echo final qui annonce http://localhost:8091/.

arreter.sh

Comme la semaine 1 : docker stop, docker rm. Pour Pygame et Qt, ajoutez xhost -local:docker a la fin pour ne pas laisser le serveur X expose.

Une fois ecrits : chmod +x *.sh.

La sequence ./construire.sh "Mon Nom" 1234567 ; ./demarrer.sh ; ./arreter.sh fait le tour complet sans intervention manuelle.
07

Finaliser le README et le .dockerignore

Documenter votre travail et exclure du contexte de build ce qui n'a rien a y faire.

.dockerignore par option

PYGAME / QT

A exclure : __pycache__/, *.pyc, sons/ (genere au build pour Qt), captures, scripts .sh, README, .dockerignore lui-meme.

THREEJS

A exclure imperativement : node_modules/, dist/, .vite/. Sans ca, votre contexte de build pese plusieurs centaines de Mo et chaque rebuild devient lent.

Test : docker build --progress=plain ... doit afficher un contexte sous les 5 Mo.

README.md - sections obligatoires

  • Ce que c'est : option choisie, scenario du Zoo, ce que voit le visiteur.
  • Comment lancer : sequence des trois scripts avec un exemple complet (vrais nom et matricule).
  • Ce qu'on voit : URL ou comportement attendu, capture d'ecran, banniere visible.
  • Choix techniques : pourquoi cette option, pourquoi ces dependances, comment la banniere se propage.
  • Pieges rencontres : ce qui a coince et comment vous l'avez resolu (X11, son, multi-stage, taille d'image).
Quelqu'un qui n'a pas vu votre travail peut lancer la borne sur son poste en suivant uniquement votre README. Pour les options Pygame et Qt, votre README explique clairement les pre-requis X11 (et PulseAudio).

Defis optionnels par option

Defi A (Pygame) - Ajouter une question sur votre cegep

Ajoutez une question personnelle dans le tableau du quiz : "Quel oiseau peut-on observer depuis la fenetre du cegep de Matane / Rimouski en mai ?" avec quatre choix dont un correct. Modifiez le code et rebuilez.

Defi B (Qt) - Ajouter une espece avec son son

Ajoutez une dixieme espece (par exemple le castor) dans le catalogue, et ajoutez sa fonction de generation dans generer-sons.py (sons de roulette de bois sur l'eau, suggestion : oscillateurs filtres). Verifiez que le son est genere au build et ecoutable au runtime.

Defi C (three.js) - Effet visuel

Ajoutez un brouillard scene.fog = new THREE.FogExp2(0x..., 0.02) ou un soleil couchant (gradient du ciel). Ne touchez pas a la geometrie de la mer pour ne pas casser les performances mobiles.

Defi commun - Reduire la taille de l'image

Mesurez avec docker images zoo-borne-medias-*. Pour Pygame : passer a python:3.12-alpine (gain ~80 Mo, mais attention aux wheels SDL2 qui peuvent ne pas etre disponibles). Pour Qt : impossible de descendre sous 600 Mo, c'est Qt6 qui pese. Pour three.js : essayer nginx:alpine-slim (gain de quelques Mo).