TP13 : Évaluation Docker
Contexte
Vous allez construire une stack Docker complète autour d'une API Node.js en appliquant l'ensemble des compétences vues pendant le cours : Dockerfile propre, Compose multi-services, registry privé, load-balancing Nginx, sécurité, observabilité, gestion des volumes et pipeline CI/CD.
Le rendu est individuel.
Modalités de rendu
Vous rendez un dépôt Git contenant l'ensemble de vos fichiers. Le lien vers le dépôt est déposé sur Teams.
Le dépôt doit contenir un fichier README.md à la racine qui sert de rapport de rendu. C'est ce fichier que je vais ouvrir en premier. Il doit présenter chaque partie dans l'ordre, avec les captures d'écran intégrées directement dans le Markdown, de façon à ce que la correction puisse se faire entièrement depuis l'interface web de GitHub/GitLab sans avoir à cloner le projet.
Le carnet de notes est rendu séparément, au format de votre choix (Notion, PDF...), directement dans le devoir Teams, attention toutefois, je ne dois avoir de problème à ouvrir votre carnet (ex: pas de lien vers un Notion privé) sinon je noterais 0/10. Le carnet doit être relativement agréable à lire. Je ne demande pas un rapport de 100 pages, mais un effort de mise en forme est attendu (titres, sous-titres, etc.) pour que la lecture soit fluide.
Consignes
Partie 1 — API & Dockerfile
Vous devez créer une API Node.js avec Express dans un dossier api/.
L'API expose deux routes :
- GET / répond en JSON avec le hostname du conteneur (os.hostname()), la valeur de la variable d'environnement PET (qui vaut dog ou cat) et un compteur du nombre de requêtes reçues depuis le démarrage du conteneur.
- GET /healthz répond avec { "status": "ok" } et le code HTTP 200. Cette route est utilisée par le Healcheck Docker pour vérifier que l'application est opérationnelle.
Le compteur est fait grâve a library prom-client et exposé sur la route /metrics pour être scrappé par Prometheus plus tard.
Le Dockerfile doit utiliser node:20-alpine comme image de base, copier uniquement les fichiers nécessaires (pas de node_modules, pas de .env), installer les dépendances avec npm install --only=production, et faire tourner l'application sous un utilisateur non-root.
Un fichier .dockerignore doit être présent pour exclure node_modules, .env et .git du contexte de build.
Le Healcheck doit cibler la route /healthz et être configuré avec un interval, un timeout et un start_period adaptés.
| Critère évalué | Points |
|---|---|
Routes / et /healthz fonctionnelles : GET / retourne hostname, PET et compteur ; GET /healthz répond 200 avec { "status": "ok" } |
1,50 |
Dockerfile : node:20-alpine, utilisateur non-root, .dockerignore présent, npm install --only=production |
0,50 |
Dockerfile : Healcheck configuré sur /healthz avec des paramètres adaptés |
1,00 |
Partie 2 — Registry privé
Vous devez déployer un registry Docker privé en local avec une interface web, pousser votre image API dessus, puis l'utiliser depuis votre stack principale.
Le registry (registry:2) et son interface web (joxit/docker-registry-ui) sont décrits dans un fichier docker-compose.registry.yml séparé, lancé indépendamment de la stack principale.
Votre image API est taguée et poussée vers localhost:5000/mon-api:1.0.0 (ou équivalent).
Rendu attendu pour cette partie : une capture d'écran de l'interface web (http://localhost:8080) montrant votre image listée dans le registry, et le champ image: de votre docker-compose.yml principal pointant vers localhost:5000/... (visible dans le fichier versionné dans le repo).
| Critère évalué | Points |
|---|---|
| Registry et interface web déployés et fonctionnels (capture de l'interface web avec l'image listée) | 1,00 |
Image API poussée vers le registry privé et utilisée dans le docker-compose.yml principal |
1,00 |
Partie 3 — Stack Compose & Nginx
Vous devez créer un fichier docker-compose.yml qui orchestre trois services sur un réseau Docker personnalisé :
cat: une instance de votre API avec la variable d'environnementPET=cat. Ce service n'expose aucun port directement à l'hôte, il est uniquement accessible depuis le réseau interne Docker.dog: une instance de votre API avec la variable d'environnementPET=dog. Même configuration réseau quecat.nginx: un reverse proxy et load balancer qui écoute sur le port 80 (ou équivalent) de l'hôte et distribue le trafic verscatetdog.
Les services cat et dog doivent démarrer uniquement quand leur healthcheck est passé. Nginx doit dépendre de cat et dog avec la condition service_healthy.
La configuration Nginx doit :
- distribuer les requêtes sur
GET /en round-robin entrecatetdog(les deux dans le mêmeupstream) ; - rediriger
GET /catexclusivement vers le servicecat; - rediriger
GET /dogexclusivement vers le servicedog.
| Critère évalué | Points |
|---|---|
docker-compose.yml avec les trois services sur un réseau personnalisé, variables d'environnement correctes, aucun port exposé pour cat et dog |
2,00 |
Configuration Nginx avec un upstream round-robin sur / équilibrant entre les deux services |
1,0 |
Locations /cat et /dog redirigeant exclusivement vers le bon service |
0,5 |
depends_on avec condition: service_healthy sur cat et dog |
0,5 |
Partie 4 — Sécurité
Toutes les valeurs configurables de votre stack doivent passer par des variables d'environnement définies dans un fichier .env. Cela inclut au minimum le port de l'API, la valeur de PET pour chaque service, et le port exposé par Nginx.
Aucune valeur ne doit apparaître en dur dans le docker-compose.yml ni dans le Dockerfile (hors valeurs par défaut techniques).
Vous devez scanner votre image API avec Trivy et justifier votre choix d'image de base : pourquoi node:20-alpine plutôt que node:latest ? Quel est l'impact sur le nombre de CVE ?
Le Dockerfile doit copier uniquement les fichiers strictement nécessaires : d'abord package*.json pour profiter du cache Docker lors des npm install, puis le reste du code. Le dossier node_modules ne doit jamais entrer dans le contexte de build.
Rendu attendu pour cette partie : une capture d'écran d'une partie de la sortie de trivy image <votre-image> dans le dossier captures/, et la justification du choix d'image.
| Critère évalué | Points |
|---|---|
| Scan Trivy effectué et capture jointe, justification du choix d'image | 0,5 |
Toutes les valeurs configurables passent par le fichier .env (port, PET, etc.), sans aucune valeur en dur dans les fichiers de config |
1,0 |
Dockerfile : COPY package*.json en premier, puis COPY . ., sans node_modules dans le contexte de build |
0,5 |
Partie 5 — Validation de la stack
Rendu attendu pour cette partie : quatre captures d'écran dans le dossier captures/, correspondant aux critères ci-dessous.
| Critère évalué | Points |
|---|---|
docker compose ps montre tous les services en état Up (healthy) |
1 |
En rafraîchissant http://localhost/ plusieurs fois, les hostnames dans la réponse JSON alternent entre les deux conteneurs (deux appels successifs côte à côte) |
1 |
/cat répond avec PET: cat, /dog avec PET: dog, et les compteurs diffèrent entre les deux services |
1 |
Partie 6 — Questions théoriques
Ces trois questions doivent être répondues dans le README.md de votre dépôt (comme le reste des questions de cette évaluation), dans une section dédiée.
- Question Swarm (1 pt) : Expliquez la différence entre
docker compose upetdocker stack deploy. Pourquoi la directivebuild:n'est-elle pas utilisable dans une stack déployée en mode Swarm ? - Question Secrets (1 pt) : Expliquez la différence entre passer un mot de passe via une variable d'environnement et via un Docker Secret. Dans quel fichier le secret est-il accessible à l'intérieur du conteneur, et comment le lire depuis du code Node.js ?
- Question Backup (1 pt) : Dans une architecture Docker en production, quels éléments faut-il impérativement sauvegarder pour pouvoir reconstruire entièrement la stack après une panne ? Distinguez ce qui est recréable automatiquement de ce qui est irremplaçable.
Partie 7 — Observabilité & Production
A la suite des deux premières parties, vous devez ajouter à votre stack de quoi monitorer votre application.
Le but ici est de faire un dashboard grafana complet avec un node exporter, cadvisor et le client prometheus pour scrapper les métriques de votre application (exposées sur /metrics grâce à prom-client).
| Critère évalué | Points |
|---|---|
| Prometheus et Grafana ajoutés à la stack et accessibles | 0,5 |
| Dashboard Grafana provisionné automatiquement (fichier de provisioning versionné dans le dépôt) | 1 |
Fichier docker-compose.prod.yml en override avec limites CPU et RAM sur chaque service |
1,00 |
| Portainer ajouté à la stack et accessible | 0,5 |
Partie 8 — Volumes
Votre stack utilise des volumes à plusieurs endroits (registry, Grafana, configuration Nginx et Prometheus).
Vous devez :
- Utiliser des volumes nommés pour les données persistantes (données Grafana, données du registry).
- Utiliser des bind mounts pour les fichiers de configuration injectés depuis l'hôte (prometheus.yml, default.conf Nginx, fichiers de provisioning Grafana).
Rendu attendu pour cette partie : une capture de docker volume ls montrant les volumes nommés de votre stack, et une capture de docker volume inspect <volume> sur l'un d'eux.
| Critère évalué | Points |
|---|---|
| Volumes nommés pour les données et bind mounts pour les configs, cohérents dans tous les fichiers Compose | 1 |
Capture de docker volume ls et docker volume inspect, justification du choix dans le README.md |
1 |
Partie 9 — CI/CD avec GitHub Actions
Vous devez créer un fichier .github/workflows/docker.yml qui automatise le build et le contrôle de sécurité de votre image API à chaque push.
Le workflow doit :
- Se déclencher sur chaque push sur la branche main.
- Builder l'image Docker de votre API.
- Scanner l'image avec Trivy et faire échouer le pipeline si des CVE de sévérité CRITICAL sont détectées.
- Pousser l'image vers un registry public au choix : Docker Hub (docker.io/<user>/mon-api:git-abc1234) ou GitHub Container Registry (ghcr.io/<user>/mon-api:git-abc1234), avec un tag basé sur le SHA court du commit.
Docker Hub nécessite de configurer deux secrets GitHub :
DOCKERHUB_USERNAMEetDOCKERHUB_TOKEN. ghcr.io utilise leGITHUB_TOKENfourni automatiquement par GitHub Actions, aucun secret supplémentaire n'est nécessaire.
Rendu attendu pour cette partie : le fichier .github/workflows/docker.yml versioné dans le dépôt, et une capture d'écran de l'onglet Actions de GitHub montrant le pipeline en succès (ou en échec justifié si des CVE CRITICAL sont présentes).
| Critère évalué | Points |
|---|---|
Workflow déclenché sur push sur main, étape de build fonctionnelle (capture de l'onglet Actions) |
1 |
Scan Trivy intégré dans le pipeline, le pipeline échoue si des CVE CRITICAL sont détectées |
1 |
Image poussée vers Docker Hub ou ghcr.io avec un tag SHA Git |
1 |
Partie 10 — Déploiement sur VPS
Si l'ensemble de votre stack est déployée et accessible depuis un VPS (serveur distant), vous obtenez les points de cette partie. Fournissez l'URL ou l'IP publique dans le README.md, ainsi qu'une capture montrant la stack fonctionnelle depuis le serveur.
| Critère évalué | Points |
|---|---|
| Stack complète déployée sur VPS, accessible depuis un navigateur externe (URL fournie dans le README) | 2 |
docker compose ps exécuté sur le VPS montrant tous les services Up (healthy) (capture depuis le serveur) |
1 |
Partie 11 — Clarté & lisibilité du README
Le README.md est le fichier rendu qui centralise votre travail. Il doit être structuré, lisible et permettre de retrouver rapidement les preuves attendues.
| Critère évalué | Points |
|---|---|
| Structure claire : titres hiérarchisés, sections dans l'ordre des parties, captures bien intégrées et légendées | 1 |
| Rédaction soignée : phrases complètes, mise en forme visible, absence de fautes grossières | 1 |
Arborescence attendue
evaluation/
.github/
workflows/
docker.yml
api/
app.js
[...]
nginx/
[...]
monitoring/
[...]
docker-compose.yml
docker-compose.registry.yml
docker-compose.prod.yml
README.md
captures/
Grille de notation récapitulative
Note 1 — Devoir
| Partie | Critère | Points |
|---|---|---|
| 1 — API & Dockerfile | Routes / et /healthz fonctionnelles avec les bonnes réponses |
1,5 |
Dockerfile : node:20-alpine, non-root, .dockerignore, --only=production |
0,5 | |
Dockerfile : healthcheck sur /healthz avec paramètres adaptés |
1 | |
| 2 — Registry privé | Registry + UI déployés, capture de l'interface avec l'image listée | 1 |
Image poussée vers le registry et utilisée dans docker-compose.yml |
1 | |
| 3 — Stack Compose & Nginx | 3 services, réseau perso, variables d'env, ports internes uniquement | 2 |
Upstream round-robin sur / entre cat et dog |
1 | |
Locations /cat et /dog vers le bon service |
0,5 | |
depends_on: condition: service_healthy sur cat et dog |
0,5 | |
| 4 — Sécurité | Scan Trivy + capture, justification image dans le carnet | 0,5 |
Toutes les valeurs via .env, aucune valeur en dur |
1 | |
COPY package*.json en premier, pas de node_modules dans le contexte |
0,5 | |
| 5 — Validation | docker compose ps tous Up (healthy) |
1 |
| Load balancing visible (hostnames alternés) | 1 | |
/cat → PET: cat, /dog → PET: dog + compteurs différents |
1 | |
| 6 — Questions théoriques | Question Swarm (dans le README) | 1 |
| Question Secrets (dans le README) | 1 | |
| Question Backup (dans le README) | 1 | |
| 7 — Observabilité & Production | Prometheus + Grafana accessibles | 1 |
| Dashboard Grafana provisionné automatiquement | 0,5 | |
docker-compose.prod.yml avec limites CPU/RAM |
1 | |
| Portainer dans la stack | 0,5 | |
| 8 — Volumes | Volumes nommés pour les données, bind mounts pour les configs, cohérents dans la stack | 1 |
Capture docker volume ls + inspect, justification dans le README |
1 | |
| 9 — CI/CD GitHub Actions | Workflow push main + build fonctionnel (capture Actions) |
1 |
| Scan Trivy en CI, pipeline échoue si CVE CRITICAL | 1 | |
Push vers Docker Hub ou ghcr.io avec tag SHA Git |
1 | |
| 10 — VPS | Stack déployée sur VPS, accessible publiquement (URL dans le README) | 2 |
docker compose ps sur le VPS, tous Up (healthy) |
1 | |
| 11 — Clarté du README | Structure, sections dans l'ordre, captures légendées | 1 |
| Rédaction soignée, mise en forme visible | 1 | |
| Total devoir | 30 |
Note 2 — Carnet
| Note | Critères |
|---|---|
| 10/10 | Carnet complet. Réponses claires, précises et bien rédigées. Justifications des choix techniques présentes. |
| 8/10 | Carnet complet. Quelques imprécisions ou manque de détails mineurs. |
| 6/10 | Carnet globalement correct, mais plusieurs points sont peu développés ou manquants. |
| 4/10 | Carnet partiellement rempli. Réponses incomplètes ou peu détaillées. |
| 2/10 | Carnet très insuffisant. Peu d'efforts visibles. |
| 0/10 | Carnet non rendu ou lien inaccessible. |