LANTORIANDocker, pas à pas

Chapitre 430 min2 exercices

Écrire un Dockerfile

Un Dockerfile est une recette : une instruction par ligne, exécutées de haut en bas. Le résultat est une image.

Les instructions utiles

Il en existe une vingtaine. Ces neuf couvrent presque tout.

InstructionRôle
FROML'image de départ. Toujours la première ligne.
WORKDIRLe dossier courant pour la suite (le crée s'il n'existe pas).
COPYCopie des fichiers de ton projet vers l'image.
RUNExécute une commande pendant la construction (installer un paquet...).
ENVDéfinit une variable d'environnement.
ARGUne variable disponible uniquement pendant la construction.
EXPOSEIndique le port écouté. C'est de la documentation, ça ne publie rien.
USERL'utilisateur qui exécute la suite. Évite de tourner en root.
CMDLa commande lancée au démarrage du conteneur. Une seule par image.

Ton premier Dockerfile

Une mini-API Node avec Express. Crée un dossier avec ces trois fichiers.

package.json
{
  "name": "mini-api",
  "private": true,
  "dependencies": { "express": "^5.1.0" }
}
server.js
const express = require("express");
const app = express();

app.get("/", (req, res) => res.json({ message: "Bonjour depuis un conteneur" }));

app.listen(3000, () => console.log("API prête sur le port 3000"));
Dockerfile
# 1. Image de départ : Node 24, variante légère
FROM node:24-slim

# 2. Dossier de travail dans le conteneur
WORKDIR /app

# 3. D'abord les dépendances (change rarement)
COPY package*.json ./
RUN npm ci

# 4. Ensuite le code (change tout le temps)
COPY . .

# 5. Documente le port et lance l'application
EXPOSE 3000
CMD ["node", "server.js"]

Il manque le fichier package-lock.json attendu par npm ci. Génère-le sans installer Node, grâce à un conteneur jetable, puis construis et lance l'image :

Terminal
# --user évite que le fichier créé appartienne à root
docker run --rm --user "$(id -u):$(id -g)" -e npm_config_cache=/tmp/npm \
  -v "$PWD":/app -w /app node:24-slim npm install --package-lock-only
docker build -t mini-api:1.0 .
docker run --rm -p 3000:3000 mini-api:1.0

Le point final de docker build est important : c'est le contexte, le dossier envoyé à Docker. -t donne un nom et un tag à l'image.

Les couches et le cache

Chaque instruction produit une couche. Au build suivant, Docker réutilise les couches inchangées. Mais dès qu'une couche change, toutes celles du dessus sont reconstruites. L'ordre des lignes décide donc de la vitesse de tes builds.

Chaque instruction crée une couche. Modifie le code et regarde quelles couches Docker doit reconstruire.

.dockerignore

COPY . . copie tout le dossier, y compris ce qui n'a rien à faire dans l'image. Un fichier .dockerignore à côté du Dockerfile l'exclut. Le build est plus rapide et tes secrets restent dehors.

.dockerignore
node_modules
vendor
.git
.env
.next
storage/logs

Le build multi-étapes

Un Dockerfile peut contenir plusieurs FROM. Les premières étapes compilent, la dernière ne garde que le résultat. L'image finale ne contient ni les outils de build ni le code source inutile. Tu en écriras un pour Next.js au chapitre 8.

Le principe
FROM node:24-slim AS build
WORKDIR /app
COPY . .
RUN npm ci && npm run build

FROM node:24-slim
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]
Exercice

Construis et versionne ton image

  1. Crée les trois fichiers de la section précédente, construis mini-api:1.0 et lance-la en arrière-plan.
  2. Vérifie la réponse avec curl localhost:3000 ou ton navigateur.
  3. Change le message, construis mini-api:1.1 et remplace le conteneur par cette nouvelle version.
Ce que tu dois obtenir

curl localhost:3000 renvoie {"message":"Bonjour depuis un conteneur"}. docker images mini-api liste deux tags, 1.0 et 1.1, avec des tailles proches.

Un indice

Modifie le message dans server.js, puis relance docker build avec un nouveau tag. Une image ne se modifie jamais : on en construit une nouvelle.

Voir la solution
Terminal
docker build -t mini-api:1.0 .
docker run -d --name api -p 3000:3000 mini-api:1.0
curl localhost:3000
# Modifie le message dans server.js, puis :
docker build -t mini-api:1.1 .
docker rm -f api
docker run -d --name api -p 3000:3000 mini-api:1.1
curl localhost:3000
docker images mini-api
Exercice

Mesure l'effet du cache

  1. Modifie une ligne de server.js, relance le build avec --progress=plain et repère les étapes marquées CACHED.
  2. Déplace COPY . . juste après WORKDIR, supprime COPY package*.json ./, et recommence.
  3. Compare. Remets ensuite le bon ordre.
Ce que tu dois obtenir

Avec le bon ordre, la ligne RUN npm ci affiche CACHED après une modification de server.js. Avec COPY . . en premier, npm ci est relancé à chaque fois.

Voir la solution

Dockerfile volontairement mal ordonné :

Dockerfile
FROM node:24-slim
WORKDIR /app
COPY . .
RUN npm ci
CMD ["node", "server.js"]
Terminal
docker build --progress=plain -t mini-api:test .
Échap