01 Guide pas-a-pas - Atelier 1 - Semaine 2

Dockerisez la borne
d'accueil Next.js

Vous recevez une application Next.js du studio creatif et vous l'emballez dans une image Docker, version debutant : un seul Dockerfile, construit ligne par ligne. A chaque ajout on regarde le contenu de l'image avec la commande ls, on lance d'abord la borne au premier plan pour prouver qu'elle marche, puis on la passe en service permanent avec -d et --restart.

Ce qu'on vous fournit

Le dossier labos/semaine-2/demande-1-borne-accueil/ contient :

Vous y ajoutez, a cote de application/ (pas dedans) : un Dockerfile d'une seule etape, un .dockerignore, trois scripts construire.sh, demarrer.sh, arreter.sh, et votre README.md.

Avant de commencer

Pre-requis

Vue d'ensemble

Le parcours en huit etapes

L'idee de ce guide : ne rien ecrire a l'aveugle. On construit le Dockerfile ligne par ligne et, grace au "truc du ls", on regarde a chaque etape ce que Docker a vraiment mis dans l'image avant d'avancer. On finit par une borne qui revient toute seule apres un redemarrage du poste.

Etape 01

Inspecter et tester l'app

Etape 02

Le truc du ls : conteneur vide

Etape 03

Copier le package.json

Etape 04

Installer les dependances

Etape 05

Apporter le code source (COPY)

Etape 06

Donner sa vraie commande a la borne

Etape 07

Preuve : lancer au premier plan

Etape 08

Mise en service : -d et --restart

01

Inspecter et tester le materiel sans Docker

Comprenez ce que fait l'application avant de l'emballer. Si Node.js >= 18 est installe sur votre poste, lancez-la en local. Sinon, lisez le code, c'est court.

Le contenu fourni

application/
application/
  package.json        # next, react, react-dom
  next.config.mjs     # config Next.js minimale
  app/
    layout.js         # squelette HTML racine
    page.js           # carte SVG du parc + horaires
    globals.css       # styles de la borne

Tester en local (si Node est installe)

Terminal
$ cd application/
$ npm install
$ npm run dev

Ouvrez http://localhost:3000/. Vous voyez la carte SVG du parc avec quatre pavillons cliquables (foret, marin, prairie, voyageurs) et l'onglet "Horaires".

Si Node n'est pas installe, ce n'est pas grave : vous testerez directement avec Docker. C'est precisement l'interet de la suite : fournir une image autonome qui tourne sans rien installer.

Vous savez decrire en une phrase ce que la borne montre au visiteur, et vous savez que la commande qui la lance est npm run dev.
02

Le truc du ls : un premier conteneur (presque) vide

Plutot que d'ecrire tout le Dockerfile d'un coup et d'esperer, on le construit ligne par ligne. Le truc : pendant tout le travail, la derniere ligne est CMD ["ls", "/app"]. A chaque ajout, on reconstruit, on lance, et le conteneur nous montre ce qu'il y a vraiment dans /app. On remplacera ce ls par la vraie commande tout a la fin.

Pendant ce travail au ls, on ne met pas encore -d ni --restart : on veut voir la sortie tout de suite, au premier plan. On les remettra a l'etape 8, une fois que tout marche.

Creez le Dockerfile (a cote de application/, pas dedans)

On commence au strict minimum : une image de base et un dossier de travail. Rien n'est encore copie.

Dockerfile
FROM node:20
WORKDIR /app

EXPOSE 3000
CMD ["ls", "/app"]

Construire, puis lancer pour voir

Terminal
$ docker build -t borne-accueil .
$ docker run --rm borne-accueil
# (aucune sortie : le dossier /app est vide)

Le conteneur execute ls /app puis s'arrete. Il n'affiche rien : WORKDIR /app a bien cree le dossier, mais on n'a encore rien mis dedans. C'est notre point de depart : une image qui tourne, un dossier vide.

La commande docker run --rm borne-accueil se termine sans afficher de fichier. L'image se construit sans erreur.
03

Copier le package.json (et le verifier au ls)

On apporte un seul fichier : package.json, la liste des dependances. On le copie en premier parce qu'il change rarement (Docker pourra reutiliser cette etape en cache). Toujours avec le ls pour verifier.

Ajoutez la ligne COPY

Dockerfile
FROM node:20
WORKDIR /app
COPY application/package.json .

EXPOSE 3000
CMD ["ls", "/app"]

Reconstruire et relancer

Terminal
$ docker build -t borne-accueil .
$ docker run --rm borne-accueil
package.json

Cette fois /app contient exactement une chose : package.json. Pas de node_modules (on n'a pas encore installe), pas de code source (pas encore copie). Le ls confirme que COPY n'a apporte que ce qu'on lui a demande.

On confond souvent "j'ai copie package.json" avec "j'ai copie l'app". Non : COPY application/package.json . ne copie qu'un seul fichier. Le ls le prouve noir sur blanc.
La sortie de docker run --rm borne-accueil affiche package.json, et rien d'autre.
04

Installer les dependances (npm install)

Maintenant que package.json est dans l'image, on lance npm install. On verifie encore au ls ce que ca a change.

Ajoutez la ligne RUN

Dockerfile
FROM node:20
WORKDIR /app
COPY application/package.json .
RUN npm install

EXPOSE 3000
CMD ["ls", "/app"]

Reconstruire et relancer

Terminal
$ docker build -t borne-accueil .
$ docker run --rm borne-accueil
node_modules
package.json

Un nouveau venu : node_modules. C'est npm install qui l'a cree a partir de package.json. Le code de l'application n'est toujours pas la : on s'en occupe a l'etape suivante.

Le premier npm install prend 1 a 2 minutes : c'est normal, il telecharge les dependances. Tant que vous ne touchez pas a package.json, Docker reutilise le cache de cette etape et les builds suivants redeviennent rapides. C'est tout l'interet d'avoir copie package.json avant le code.
La sortie de docker run --rm borne-accueil affiche node_modules et package.json.
05

Apporter le code source, et le prouver avec ls

Derniere chose a faire entrer dans l'image : tout le dossier application/. On verifie une derniere fois au ls que les fichiers sont bien arrives.

Ajoutez une seconde ligne COPY

On apporte le code apres le npm install (le code change souvent, les dependances rarement). On garde le CMD ls le temps de verifier.

Dockerfile
FROM node:20
WORKDIR /app
COPY application/package.json .
RUN npm install
COPY application .

EXPOSE 3000
CMD ["ls", "/app"]

Reconstruire et relancer

Terminal
$ docker build -t borne-accueil .
$ docker run --rm borne-accueil
app
next.config.mjs
node_modules
package-lock.json
package.json

Cette fois /app contient bien le dossier app/, next.config.mjs, etc. C'est la preuve que COPY application . a apporte tout l'arbre source dans l'image.

Vous pouvez meme regarder a l'interieur du sous-dossier app/ en passant la commande directement au docker run (ce qui ignore le CMD le temps d'un coup d'oeil) :

Terminal
$ docker run --rm borne-accueil ls /app/app
globals.css
layout.js
page.js
Attention au .dockerignore (etape 8). Sans lui, le COPY application . emporte aussi application/node_modules et application/.next de votre poste : contexte enorme, build lent, image polluee. On regle ca a la fin, une fois que tout marche.
La sortie de ls /app montre maintenant app, next.config.mjs et package.json (en plus de node_modules). Le code est dans l'image.
06

Donner sa vraie commande de demarrage a la borne

Le ls a fait son travail : il nous a servi a verifier chaque ajout. On le remplace maintenant par la vraie commande qui demarre l'application.

Remplacez le CMD

Dockerfile (version finale)
FROM node:20
WORKDIR /app
COPY application/package.json .
RUN npm install
COPY application .

EXPOSE 3000

# Avant : CMD ["ls", "/app"]  (servait juste a verifier)
CMD ["npm", "run", "dev"]

C'est le Dockerfile complet de la borne. Quelques lignes, lisibles, et chacune se justifie. npm run dev demarre le serveur Next sur le port 3000 a l'interieur du conteneur (d'ou le EXPOSE 3000).

Garder CMD ["ls", ...] par oubli : le conteneur affiche la liste des fichiers puis s'arrete aussitot, et on croit que "Docker ne marche pas". En realite il a fait exactement ce qu'on lui a demande. Verifiez toujours quel CMD est actif.

Note : version "deployable" (facultatif)

npm run dev lance le serveur de developpement, parfait pour ce labo d'initiation. Pour une vraie mise en production, on remplacerait par un build de production : RUN npm run build apres le COPY application ., puis CMD ["npm", "start"]. Pour une image plus legere, FROM node:20-slim au lieu de node:20. Ces raffinements ne sont pas requis ici ; ils sont detailles dans la version multi-stage du guide.

Votre Dockerfile final contient, dans l'ordre : FROM, WORKDIR, COPY package.json, RUN npm install, COPY application ., EXPOSE 3000, CMD ["npm","run","dev"].
07

Preuve que ca marche : lancer au premier plan

Avant de la lancer "pour de vrai" en arriere-plan, on demarre la borne au premier plan, avec un port publie. On VOIT les logs defiler et on ouvre la page dans le navigateur. C'est la preuve que l'image fonctionne.

Terminal
$ docker build -t borne-accueil .
$ docker run --rm -p 8090:3000 --name borne-accueil-en-marche borne-accueil
# Next demarre, les logs s'affichent ici. Le terminal reste "occupe".
  Next.js 14.2.5
  - Local:   http://localhost:3000
  Ready in 1.5s

Pendant que ce terminal tourne, ouvrez http://localhost:8090/ dans votre navigateur. La carte du parc s'affiche, les pavillons reagissent au clic, l'onglet Horaires fonctionne. C'est la preuve que votre image sert reellement la borne.

Le -p 8090:3000 relie le port 8090 de votre poste au port 3000 du conteneur. Pour arreter : Ctrl + C dans ce terminal. Le --rm fait que le conteneur se nettoie tout seul en s'arretant, vous pouvez donc relancer aussitot.

"La page ne repond pas" : verifiez que vous ouvrez bien le port de gauche (8090, cote hote), pas 3000. Le 3000 n'existe qu'a l'interieur du conteneur.
"port is already allocated" : un autre conteneur utilise deja 8090. Choisissez un autre port hote (ex. -p 8091:3000) ou arretez l'autre conteneur.
Pour diagnostiquer un probleme plus avance, on peut aussi entrer dans le conteneur en ligne de commande (une fois l'image construite). La marche a suivre est dans le diaporama Depannage : entrer dans un conteneur Docker.
Le terminal affiche "Ready", et http://localhost:8090/ montre la borne dans le navigateur. Vous savez l'arreter avec Ctrl+C.
08

Mise en service : -d, --restart, et les trois scripts

La borne du hall doit tourner en permanence et revenir toute seule apres une coupure de courant. On remet donc les drapeaux qu'on avait retires pendant le travail : -d pour l'arriere-plan et --restart pour le redemarrage automatique, puis on range tout dans trois scripts.

Les deux drapeaux qu'on remet a la fin

Terminal
$ docker run -d --restart unless-stopped \
    --name borne-accueil-en-marche \
    -p 8090:3000 \
    borne-accueil
  • -d (detached) : le conteneur tourne en arriere-plan, votre terminal est libre. Plus de logs qui defilent, plus de Ctrl+C.
  • --restart unless-stopped : si le poste redemarre (ou si le conteneur plante), Docker le relance automatiquement. La borne revient seule le matin, personne n'a a la rallumer. Seule exception : si vous l'avez arretee a la main avec docker stop.
Apres ce docker run -d --restart unless-stopped ..., la commande docker inspect borne-accueil-en-marche --format '{{.HostConfig.RestartPolicy.Name}}' repond unless-stopped. Apres un redemarrage du poste, docker ps montre la borne Up sans intervention.

Ranger dans trois scripts

On encapsule les commandes dans trois scripts bash en kebab-case, rendus executables avec chmod +x *.sh.

construire.sh
#!/bin/bash
set -e
docker build -t borne-accueil .
demarrer.sh
#!/bin/bash
set -e
docker run -d --restart unless-stopped \
    --name borne-accueil-en-marche \
    -p 8090:3000 \
    borne-accueil
echo "Borne accessible sur http://localhost:8090"
arreter.sh
#!/bin/bash
docker stop borne-accueil-en-marche
docker rm borne-accueil-en-marche

Le .dockerignore (a ne pas oublier)

application/node_modules et application/.next sont regeneres dans l'image par npm install ; inutile de les envoyer au build. On les exclut :

.dockerignore
# Regeneres dans l'image : inutile de les envoyer.
application/node_modules
application/.next

# Bruit et fichiers d'usage.
.git
*.log
construire.sh
demarrer.sh
arreter.sh
README.md
.dockerignore
La sequence ./construire.sh ; ./demarrer.sh rend la borne accessible sur http://localhost:8090 en arriere-plan, et elle revient seule apres un redemarrage. ./arreter.sh l'arrete proprement.

Trois defis optionnels

Defi A - Version deployable (build + start)

Remplacez CMD ["npm","run","dev"] par un vrai build de production : ajoutez RUN npm run build apres le COPY application ., puis CMD ["npm","start"]. Comparez le temps de demarrage et le comportement avec la version dev.

Defi B - Image plus legere

Passez de FROM node:20 a FROM node:20-slim et mesurez avec docker images borne-accueil. Pour descendre beaucoup plus bas (multi-stage, image finale ~25 Mo), voir la version multi-stage du guide.

Defi C - Banniere d'identification

Faites apparaitre votre nom et votre matricule dans la borne via la chaine ARG -> ENV -> NEXT_PUBLIC, ce qui prouve que votre image n'est pas identique a celle du voisin. La marche a suivre complete est dans la version multi-stage (etape 4).