Mini-test 7

Directives
Dockerfile

FROM, WORKDIR, COPY, RUN, ENV, ARG, EXPOSE, CMD, ENTRYPOINT, AS

9 questions + en savoir plus

images de base cache de couches ARG vs ENV CMD vs ENTRYPOINT

Cliquez Voir la solution sur chaque slide

← → naviguer · S solution · R recommencer

Question 1 / 9 choisir une image par projet

Associer chaque projet a son image de base

Vous avez 6 projets a containeriser. Pour chacun, choisissez l'image de base la plus appropriee (la plus haute en abstraction qui couvre le besoin). Une seule image par projet, une seule fois.

Glissez chaque image dans le projet ou elle est le bon point de depart.

httpd:2.4
nginx:alpine
php:8.3-apache
python:3.12-slim
node:20-alpine
mariadb:11
Site portfolio statique
juste du HTML, CSS, JS — aucun langage cote serveur
Forum / CMS en PHP
site dynamique en PHP, mod_php / Apache
Bot IRC en Python
script Python avec quelques pip install
API REST en Node.js
Express, npm install, app.js qui ecoute sur un port
Base de donnees pour un site
stockage relationnel, pas de Dockerfile custom necessaire
Reverse proxy devant 3 services
routage HTTP, terminaison TLS, cache statique

Principe : toujours partir de l'image la plus specifique qui couvre le besoin. Eviter ubuntu et installer un service manuellement quand une image officielle existe deja.

Pour le portfolio statique, alternative possible : nginx:alpine aurait aussi marche (sert des fichiers statiques aussi). Le choix se fait sur les preferences (Apache vs Nginx, mais les deux conviennent pour du statique).

Question 2 / 9 FROM ubuntu:24.04

FROM ubuntu:24.04 (pour servir un site PHP)

Vous partez d'ubuntu:24.04 pour servir un site PHP. Glissez chaque composant dans la bonne categorie.
FROM ubuntu:24.04

Glissez chaque composant dans la categorie correcte. Cliquez sur une etiquette deposee pour la retirer.

apt + bash + coreutils
userland Ubuntu
Apache (httpd)
mod_php
interpreteur PHP
vos fichiers PHP
CMD apache2 -DFOREGROUND
Tourne deja dans l'image
livre avec ubuntu:24.04
A faire dans le Dockerfile
RUN apt install, COPY, CMD...

Tourne deja : apt + bash + coreutils, userland Ubuntu. C'est tout.

A faire : tout le reste. Concretement :

RUN apt update && apt install -y \
    apache2 libapache2-mod-php php
COPY ./site /var/www/html/
CMD ["apache2ctl", "-D", "FOREGROUND"]

Quand choisir ubuntu malgre tout ce travail : on a besoin de paquets exotiques, scripts d'init complexes, ou plusieurs services. Sinon, partir de php:8.3-apache evite tout ce code.

Question 3 / 9 FROM httpd:2.4

FROM httpd:2.4 (pour un site statique)

Vous partez de httpd:2.4 pour servir un site statique (HTML, CSS, JS). Glissez chaque composant dans la bonne categorie.
FROM httpd:2.4

Glissez chaque composant. Cliquez sur une etiquette deposee pour la retirer.

Apache configure et lance
/usr/local/apache2/htdocs/ comme racine
EXPOSE 80 deja declare
CMD apache en avant-plan
userland Debian minimal
vos fichiers HTML/CSS/JS
Tourne deja dans l'image
livre avec httpd:2.4
A faire dans le Dockerfile
essentiellement un COPY

Tourne deja : Apache configure et lance, htdocs comme racine, EXPOSE 80, CMD pour apache en avant-plan, userland Debian minimal.

A faire : juste COPY ./site /usr/local/apache2/htdocs/. C'est tout. Le Dockerfile complet :

FROM httpd:2.4
COPY ./site /usr/local/apache2/htdocs/

Important : pas de PHP, pas de mod_php. Si on en avait besoin, on partirait plutot de php:8.3-apache.

Question 4 / 9 FROM php:8.3-apache

FROM php:8.3-apache (pour un site PHP)

Vous partez de php:8.3-apache pour servir un site PHP dynamique. Glissez chaque composant dans la bonne categorie.
FROM php:8.3-apache

Glissez chaque composant. Cliquez sur une etiquette deposee pour la retirer.

Apache + mod_php pre-actives
l'interpreteur PHP 8.3
/var/www/html/ comme racine
EXPOSE 80 deja declare
CMD pour apache en avant-plan
vos fichiers .php
Tourne deja dans l'image
livre avec php:8.3-apache
A faire dans le Dockerfile
essentiellement un COPY

Tourne deja : Apache + mod_php pre-actives, l'interpreteur PHP 8.3, /var/www/html comme racine, EXPOSE 80, CMD pour Apache en avant-plan.

A faire : juste copier vos fichiers .php. Le Dockerfile complet :

FROM php:8.3-apache
COPY ./site /var/www/html/

C'est la voie courte pour un site PHP. Tout est pre-configure : il ne reste que vos fichiers.

Pour ajouter des extensions PHP (ex: mysqli, gd), RUN docker-php-ext-install mysqli gd dans le Dockerfile.

Question 5 / 9 WORKDIR vs cd dans RUN : predire ls

Que liste RUN ls dans les 2 variantes ?

Le repertoire de build (build context) contient ces 3 entrees : README.md, package.json, et un dossier src/. Comparez 2 variantes du Dockerfile et donnez la sortie du RUN ls final dans chaque cas.
Variante A — avec WORKDIR :
FROM alpine:3.19 WORKDIR /app COPY . . RUN ls

Sortie du RUN ls final ?

Variante B — avec cd dans un RUN :
FROM alpine:3.19 RUN mkdir -p /app RUN cd /app COPY . . RUN ls

Sortie du RUN ls final ?

Variante A — avec WORKDIR :

README.md
package.json
src

Variante B — avec cd dans un RUN :

README.md
bin
dev
etc
home
lib
media
mnt
opt
package.json
proc
root
run
sbin
src
srv
sys
tmp
usr
var

Le contenu de la racine / melange avec les fichiers du build context.

Regle : dans un Dockerfile, on n'utilise jamais cd entre des RUN. Toujours WORKDIR, qui persiste entre couches.

Question 6 / 9 COPY : source et destination

COPY : qui est relatif a quoi ?

Sur votre laptop, votre projet est dans /home/nadine/dev/mon-app/. Il contient un Dockerfile et un sous-dossier sources/. Vous etes DANS ce dossier, et vous lancez docker build -t img . (le . final = le build context).
LAPTOP de Nadine /home/nadine/dev/ mon-app/ ← build context (le "." de docker build) Dockerfile README.md sources/ main.py utils.py config.yaml $ pwd → /home/nadine/dev/mon-app docker build -t img . le "." = build context IMAGE DOCKER (img) / /app/ ← WORKDIR (= cwd des couches suivantes) code/ main.py utils.py config.yaml copies par COPY ./sources /app/code Source = relative au build context (cote LAPTOP). Destination = relative au WORKDIR (cote IMAGE).
WORKDIR /app COPY ./sources /app/code

(a) Sur le laptop, ou est physiquement ./sources avant le COPY ? Donnez le chemin absolu.
(b) Et ou arrive son contenu dans l'image ?
(c) Et que ferait COPY . . au lieu de COPY ./sources /app/code ?

(a) Avant le COPY, sur le laptop : /home/nadine/dev/mon-app/sources/

Le path ./sources du Dockerfile est relatif au build context, qui est le dernier argument de docker build (ici . = le repertoire courant = /home/nadine/dev/mon-app).

(b) Apres le COPY, dans l'image : /app/code/ (avec main.py, utils.py, config.yaml)

La destination /app/code est un chemin absolu DANS l'image. (Si la destination etait relative comme ./code, ce serait relatif au WORKDIR courant.)

(c) COPY . . :

Regle absolue : on ne peut PAS faire COPY ../autre-dossier : on ne peut JAMAIS sortir du build context (securite + reproductibilite).

Astuce : .dockerignore filtre ce qui sera dans le build context AVANT que COPY le voit. Indispensable pour eviter de copier node_modules, .git, etc.

Question 7 / 9 ENV vs ARG (drag & drop)

ENV vs ARG : quand vit chacune ?

ENV et ARG sont les deux directives de variables d'un Dockerfile. Elles ont l'air similaires mais vivent dans des temps differents. Glissez chaque caracteristique dans la directive concernee.

Une caracteristique peut aller dans une seule colonne. Cliquez sur une etiquette deposee pour la retirer.

Disponible au runtime du container
Disponible pendant le build seulement
Visible via docker inspect
Disparait apres le build
Modifiable avec docker run -e KEY=val
Modifiable avec --build-arg KEY=val
Lue par l'app (via getenv / process.env)
Typique : version, commit hash, date de build
ENV
variable d'environnement, persistante dans l'image, vue par le container au runtime
ARG
argument de build, ephemere, n'existe que pendant docker build

ENV (4) : runtime, visible via docker inspect, modifiable avec docker run -e, lue par l'app via getenv / process.env. Persiste dans l'image finale.

ARG (4) : build seulement, disparait apres le build, modifiable avec --build-arg, typique pour version / commit hash / date de build. N'existe pas au runtime.

Le pont entre les deux : ENV qui prend la valeur d'un ARG

ARG VERSION=dev ENV APP_VERSION=$VERSION

L'ARG est temporaire (build seulement), mais on l'utilise pour graver sa valeur dans une ENV qui, elle, persiste. Au runtime, l'app peut lire APP_VERSION via les variables d'environnement.

Verifier les ENV d'une image deja construite :

docker inspect img --format '{{.Config.Env}}'

Note : un ARG sans valeur par defaut (ex: ARG BUILD_DATE) doit etre passe via --build-arg, sinon il est vide.

Question 8 / 9 EXPOSE + docker run + browser

EXPOSE + docker run : quel ensemble est coherent ?

Le Dockerfile contient deja : EXPOSE 80. L'image servira un site web. Vous voulez ouvrir http://localhost:8080 dans votre navigateur et tomber sur l'app. Quelle combinaison docker run + URL est coherente ?
FROM php:8.3-apache COPY ./site /var/www/html/ EXPOSE 80

Vous voulez ouvrir http://localhost:8080 dans votre navigateur. Choisissez d'abord la commande docker run, puis l'URL que vous tapez dans le browser.

$_ 1. Quelle commande pour lancer le container ?
docker run -p 8080:80 img
docker run -p 80:8080 img
docker run img
docker run -P img
2. Quelle URL tapez-vous dans le navigateur ?
http://localhost:80
http://localhost:8080
http://container:80
http://localhost:32768

Bonnes reponses : 1=A (docker run -p 8080:80 img), 2=B (http://localhost:8080).

Format -p HOTE:CONTAINER : on relie le port 8080 de l'hote (cote machine, cote browser) au port 80 du container (cote application, le port que Apache ecoute). Le browser tape localhost:8080, ca arrive sur le port 80 du container, donc sur Apache.

Pourquoi pas les autres docker run ?

Pourquoi pas les autres URLs ?

Memo : -p HOTE:CONTAINER — le 1er nombre = ce que tu tapes dans le browser, le 2e = le port que l'app ecoute dans le container.

Question 9 / 9 CMD vs ENTRYPOINT

CMD vs ENTRYPOINT : la difference

Ces deux Dockerfiles different par une seule ligne :
# Version A CMD ["python", "bot.py"] # Version B ENTRYPOINT ["python", "bot.py"]

Que fait docker run img --debug dans chaque version ?

Version A (CMD) :

L'argument du docker run remplace le CMD au complet. Le container lance :

--debug    # echec : --debug n'existe pas comme commande

Version B (ENTRYPOINT) :

L'argument du docker run est ajoute a l'ENTRYPOINT. Le container lance :

python bot.py --debug    # le mode debug du bot

Resume :

Surcharger un ENTRYPOINT (rare, mais possible) :

docker run --entrypoint /bin/sh img

En savoir plus

Pour aller plus loin

Couches RUN, taille d'image, multi-stage

3 questions avancees

chainer les RUN alpine vs slim vs full COPY --from / multi-stage

Pourquoi 30 Mo au lieu de 1 Go

En savoir plus RUN : une couche, ou plusieurs

RUN : chainer les commandes pour reduire la taille

Quelle version produit l'image la plus petite ?
# Version A RUN apt-get update RUN apt-get install -y curl RUN apt-get clean RUN rm -rf /var/lib/apt/lists/* # Version B RUN apt-get update && \ apt-get install -y --no-install-recommends curl && \ apt-get clean && \ rm -rf /var/lib/apt/lists/*

Expliquez pourquoi la difference de taille :

Version B est plus petite.

Pourquoi : chaque RUN cree une nouvelle couche dans l'image. Une fois une couche figee, on ne peut plus rien y supprimer. Dans Version A :

Resultat : les 50 Mo des listes apt restent dans la couche 1, gonflent l'image finale.

Dans Version B, tout se passe dans une seule couche. Le rm se fait avant que la couche soit figee, donc les fichiers temporaires n'existent jamais reellement.

Regle : chainer les RUN apt avec && et un rm -rf /var/lib/apt/lists/* a la fin. Idem pour npm cache clean, yum clean all, etc.

En savoir plus AS : multi-stage teaser

AS et FROM ... AS

Vous voyez ce Dockerfile qui contient deux instructions FROM :
FROM golang:1.22 AS builder WORKDIR /src COPY . . RUN go build -o bot . FROM alpine:3.19 COPY --from=builder /src/bot /usr/local/bin/bot CMD ["/usr/local/bin/bot"]

(a) Combien y a-t-il de stages ? (b) A quoi sert le nom builder ? (c) Quelle est la taille finale de l'image ?

(a) Deux stages (separes par les deux FROM).

(b) AS builder donne un nom au premier stage pour pouvoir y faire reference plus tard avec COPY --from=builder. Sans le nom, on devrait utiliser un numero (--from=0) qui est moins lisible.

(c) L'image finale contient :

L'image golang:1.22 (~800 Mo) avec tout le compilateur Go et les sources n'est PAS dans l'image finale. C'est le point cle du multi-stage : separer le build (gros) du runtime (petit).

Autres patterns :

# Stages anonymes (acces par numero)
FROM node:20 AS stage1
FROM nginx:alpine
COPY --from=0 ...        # equivalent a --from=stage1

# Plusieurs stages, on peut tous les chainer
FROM ... AS deps
FROM ... AS builder
FROM ... AS runtime

(Le mini-test 8 Multi-stage ira plus loin sur ces patterns.)

En savoir plus optimisation : taille + multi-stage

Reduire la taille d'une image

Deux leviers complementaires pour reduire la taille d'une image Docker : (1) choisir une variante de base plus petite, et (2) faire du multi-stage avec COPY --from.

Partie A — alpine vs slim vs full : pour un bot IRC en Python, on hesite entre 3 variantes :

FROM python:3.12 FROM python:3.12-slim FROM python:3.12-alpine

Donnez (1) la taille approximative de chacune, (2) un avantage et un piege de chaque, (3) celle que vous choisiriez par defaut.

Partie B — multi-stage avec COPY --from : pour aller plus loin, on construit dans une image avec compilateur, puis on copie juste le resultat dans une image legere. Donnez un exemple de Dockerfile multi-stage pour un programme Go (ou Rust, ou C). Indiquez quelle est la taille de l'image finale par rapport a l'image de build.

Partie A - alpine / slim / full

Choix par defaut : slim. Pour la plupart des projets Python, c'est le meilleur compromis. Alpine est tentant mais cause souvent des heures de debug a cause de musl.

Meme logique pour node:20 vs node:20-slim vs node:20-alpine.

Partie B - multi-stage

FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o bot .

FROM alpine:3.19
COPY --from=builder /src/bot /usr/local/bin/bot
CMD ["/usr/local/bin/bot"]

L'image golang:1.22 n'est PAS dans l'image finale. C'est le point cle du multi-stage : separer le build (gros, outils de compilation) du runtime (petit, juste l'executable).

Encore plus petit : scratch

FROM golang:1.22 AS builder
...
FROM scratch
COPY --from=builder /src/bot /bot
CMD ["/bot"]

scratch est l'image vide absolue (0 octet). L'image finale ne contient que votre binaire. Marche pour Go (statique), Rust (avec musl). Marche PAS pour Python/Node (interpreteur necessaire).

Resume - 2 leviers :

  1. Variante de base : full -> slim -> alpine -> scratch (de + en + petit)
  2. Multi-stage : compiler dans une grosse image, copier juste le resultat dans une petite (FROM ... AS + COPY --from)

Bilan

Ce que vous venez de pratiquer

R pour recommencer.

« Retour aux mini-tests