NIVEAU 1 / 3

Fabriquer son Dockerfile

Premier Dockerfile, instructions essentielles

On part du « hello world » et on monte progressivement.
Exemples comparés : PHP, Node, Python.

Avant de fabriquer : le vocabulaire

Trois mots à ne plus confondre

Dockerfile Le fichier texte = la recette

Un simple fichier texte nommé exactement « Dockerfile » (sans extension). Il contient les instructions pour construire ton image. Tu l'écris une fois, tu le versionnes dans Git.

Image Le résultat = le plan figé

Le produit du build. Une image est immuable, en lecture seule. Tu peux la stocker, l'envoyer sur Docker Hub, la partager. Pense « modèle » ou « plan ».

Conteneur L'instance = le plan en action

Une image qui tourne. Tu peux lancer 50 conteneurs depuis la même image, chacun avec son propre état (fichiers temporaires, processus, etc.).

La chaîne complète : Dockerfile → docker build → Image → docker run → Conteneur

Le « Hello World » de Docker

3 lignes, et tu as ton premier Dockerfile

Étape 1 : Le Dockerfile

Dockerfile
# On part de l'image alpine (Linux mini, ~5 Mo)
FROM alpine:3.19

# La commande lancée au démarrage
CMD ["echo", "Hello depuis Docker !"]

C'est tout. Sauvegarde sous le nom exact « Dockerfile ».

Étape 2 : Construire et lancer

Terminal
# Construire l'image (le point = répertoire courant)
$ docker build -t hello-docker .

# Lancer un conteneur depuis cette image
$ docker run hello-docker

Hello depuis Docker !
Les 3 ingrédients minimum d'un Dockerfile
FROM L'image de base. Toujours en premier. (corps) Optionnel : COPY, RUN, ENV... pour personnaliser. CMD Ce qui se lance quand le conteneur démarre.

Astuce : commence toujours simple. Tu rajoutes des lignes une par une et tu rebuild à chaque fois.

Les instructions essentielles

Celles que tu vas écrire 95% du temps

InstructionRôleExemple
FROMImage de base à hériterFROM node:20-alpine
WORKDIRDéfinir le répertoire de travailWORKDIR /app
COPYCopier des fichiers de l'hôte vers l'imageCOPY package.json .
RUNExécuter une commande pendant le buildRUN npm install
ENVDéfinir une variable d'environnementENV NODE_ENV=production
EXPOSEDocumenter le port que l'app écouteEXPOSE 3000
CMDCommande par défaut au démarrageCMD ["node", "server.js"]
ENTRYPOINTPoint d'entrée fixe (avancé)ENTRYPOINT ["./run.sh"]

Note : les instructions sont en MAJUSCULES par convention (le parser les accepte aussi en minuscules).

Décortiquons un Dockerfile complet

Exemple Node.js - chaque ligne expliquée

Dockerfile (Node.js)
FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
EXPOSE 3000

CMD ["node", "server.js"]
  1. Image de base : Node 20 sur Alpine (~50 Mo)
  2. Tout ce qui suit se passe dans /app
  3. Copie SEULEMENT les fichiers de dépendances
  4. Installe les dépendances de prod uniquement
  5. Ensuite copie le code source (ordre important pour le cache !)
  6. Variable d'env lue par Express et autres
  7. Documente que l'app écoute sur 3000
  8. Au démarrage du conteneur : node server.js

Attention : l'ordre des lignes compte ! On y revient à la slide « cache des couches ».

Trois langages, trois Dockerfiles

Le squelette est toujours le même

PHP + Apache
FROM php:8.3-apache
RUN docker-php-ext-install \
      pdo pdo_mysql
COPY ./src/ /var/www/html/
EXPOSE 80
CMD ["apache2-foreground"]
Node.js
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Python (Flask)
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir \
      -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["python", "app.py"]

Patron commun : FROM (base) → WORKDIR (où) → COPY deps → RUN install → COPY code → EXPOSE → CMD. Le squelette est universel.

Trois pièges à éviter dès le début

Des paires d'instructions qui se ressemblent mais ne font pas pareil

COPY vs ADD
✓ COPY COPY ./src /app

Simple, prévisible : copie locale uniquement.

ADD ADD https://... /app

ADD téléchargerait des URL et déballe les .tar automatiquement. Source de surprises.

→ Utilise COPY 99% du temps.

RUN vs CMD
✓ RUN RUN apt install nginx

S'exécute pendant le build → fige le résultat dans l'image.

CMD CMD ["node", "server.js"]

S'exécute au démarrage du conteneur → l'app qui tourne.

→ Plusieurs RUN possibles, un seul CMD effectif.

CMD vs ENTRYPOINT
✓ CMD (par défaut) CMD ["node", "app.js"]

Remplaçable au lancement : docker run img autre-cmd.

ENTRYPOINT ENTRYPOINT ["./run.sh"]

Toujours exécuté, pas remplaçable. Utile pour wrappers, sinon piège.

→ Reste sur CMD pour commencer.

Le cycle build → run en pratique

Ce que tu tapes vraiment dans le terminal

1. Build l'imageÀ partir du Dockerfile
2. Run un conteneurÀ partir de l'image
3. Itèremodifie & rebuild
Terminal complet - cycle de développement
# 1. Construire (le point = Dockerfile dans le dossier courant)
$ docker build -t mon-app:1.0 .

# 2. Vérifier que l'image est bien créée
$ docker images
REPOSITORY   TAG    IMAGE ID       SIZE
mon-app      1.0    a3f8b2c1d9e0   142MB

# 3. Lancer en arrière-plan, mapper le port
$ docker run -d -p 8080:3000 --name mon-conteneur mon-app:1.0

# 4. Vérifier les logs
$ docker logs mon-conteneur

# 5. Modifier ton code, puis rebuild + relance
$ docker stop mon-conteneur && docker rm mon-conteneur
$ docker build -t mon-app:1.1 . && docker run -d -p 8080:3000 mon-app:1.1

Le tag (:1.0, :1.1, :latest) te sert à versionner tes images. Sans tag, Docker met :latest par défaut.

Erreurs typiques au premier Dockerfile

Les pièges qui font perdre une heure

❌ « Cannot connect to localhost:3000 »Cause : Tu as oublié -p 8080:3000 au docker run.✓ Ajoute -p HOTE:CONTENEUR pour mapper le port.
❌ Le conteneur s'arrête immédiatementCause : Pas de CMD, ou CMD lance un processus qui finit (script court).✓ CMD doit lancer un processus qui reste actif (serveur, daemon).
❌ « file not found » au COPYCause : Tu pointes un fichier qui n'existe pas dans le dossier de build.✓ COPY ne voit que les fichiers du dossier où tu fais le build (le « contexte »).
❌ L'app marche pas, mais pas d'erreur claireCause : Variables d'env manquantes (DATABASE_URL, etc.).✓ Ajoute des ENV dans le Dockerfile, ou -e VAR=valeur au run.
❌ Image énorme (1+ Go pour rien)Cause : Image de base trop grosse, fichiers temporaires copiés.✓ Utilise une variante :alpine ou :slim, ajoute un .dockerignore (deck 2).
❌ « permission denied » dans le conteneurCause : Fichiers copiés appartiennent à root, app tente d'écrire ailleurs.✓ Vérifie les permissions, utilise chown dans un RUN si besoin.

Ce qu'il faut retenir

Vocabulaire

Dockerfile = recette texte. Image = produit du build (figé). Conteneur = instance vivante.

Squelette universel

FROM → WORKDIR → COPY deps → RUN install → COPY code → EXPOSE → CMD. Marche pour PHP, Node, Python, Go, Ruby...

Les 8 instructions clés

FROM, WORKDIR, COPY, RUN, ENV, EXPOSE, CMD, ENTRYPOINT. Le reste viendra avec l'expérience.

Préfère

COPY (pas ADD), CMD (pas ENTRYPOINT au début), :alpine ou :slim comme base.

Cycle de dev

build -t nom:tag . / run -d -p hote:cont nom:tag / logs / stop / rm / rebuild. Itère vite.

Niveau 2 (intermédiaire) : optimisation des couches, multi-stage build, .dockerignore. Tes images vont passer de 1 Go à 100 Mo.