---
title: "Pratique : Images Docker"
subtitle: "Créer et optimiser des images Docker"
description: Exercices pratiques pour créer et optimiser des images Docker avec des applications Java simples.
image: _quarto-utils/MyMedia/images/optimized/ian-taylor-jOqJbvo1P9g-unsplash_docker-400.webp

jupyter: bash

tools:
  - docker
  - git

programs:
  - M1-InfoMath [DEV-ADV]
  - CNAM-I [PO43]

categories:
  - Practice
  - Containers
  - Docker
  - Images

provide_notes: true
provide_slides: false

---

```{bash}
#| echo: false
#| output: false

# =========================
# Docker / Compose quiet & batch friendly
# =========================

# ---- Docker Compose ----
export COMPOSE_ANSI=never
export COMPOSE_PROGRESS=quiet
export COMPOSE_DOCKER_CLI_BUILD=1

# ---- BuildKit (build docker & compose) ----
export DOCKER_BUILDKIT=1
export BUILDKIT_PROGRESS=quiet

# ---- Désactiver les suggestions CLI interactives ----
export DOCKER_CLI_HINTS=false

# ---- Format plus script-friendly ----
# export TERM=dumb

# ---- Empêche certaines invites interactives ----
export DEBIAN_FRONTEND=noninteractive
```

```{bash}
#| echo: false
#| output: false

# --quiet --batch-mode

mkdir -p .mvn && printf "%s\n" "-B" "-ntp" > .mvn/maven.config
```

:::{.callout-important}
## Corrections

```{bash}
# docker system prune --all --force --volumes
```

```{bash}
#| echo: false
#| output: asis
PROVIDER="github" && \
REPO="ebpro/notebook-containers-intro-exercices-images" && \
BRANCH="main" && \
gitpull.sh --provider github  --quiet \
  --branch ${BRANCH} ${REPO} \
  --message "La correction des exercices est disponible dans le dépôt :" && \
source get_src_dir.sh ${PROVIDER} ${REPO}

cd ${SRC_DIR}
```
:::


## EXERCICE 1 — Image Java simple

Créer une image Docker contenant une application Java simple construite avec Maven Wrapper.

### Structure du projet

Créez l’arborescence suivante :

```

hello-java/
├── src/
│   └── main/
│       └── java/
│           └── app/
│               └── App1.java
├── pom.xml
```

Etudier le code de l’application Java `App1.java` :

```{.java include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/src/main/java/app/App1.java"
    code-line-numbers="true"
    show-language="true"
    filename="src/main/java/app/App1.java"
    }
```

Etudier le fichier `pom.xml`, regarder :

  - les coordonnées du projet (groupId, artifactId, version) : identifier ton application et sa version.
  - la section properties est très importante, car elle regroupe les versions de Java et des bibliothèques ; le projet utilise Java 21.
    - la propriété `main.class` définit la classe principale de l’application (avec la méthode `main`) qui sera définit dans le manifeste du JAR exécutable.
  - La partie dependencies définit les bibliothèques externes dont le projet a besoin, comme JUnit pour les tests ou les outils de logging pour afficher des messages.
  - build/plugins, définissent comment Maven va compiler le code et lancer les tests automatiquement.
  - Enfin, la section profiles (notamment prod). Un profil Maven est une partie optionnelle de la configuration activé manuellement (ici avec `-Pprod`). Ce profile sert à créer une version prête à être distribuée, avec un JAR exécutable (dans `target`) et les dépendances copiées dans le dossier (`libe`).


```{.xml include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/pom.xml"
    code-line-numbers="true"
    show-language="true"
    filename="pom.xml" language-title="Maven POM"
   }
```

Générer le Maven Wrapper avec la commande :

```bash
mvn wrapper:wrapper
```

Tester que l'application fonctionne localement :

```{bash}
#| echo: true
#| output: true
#| label: test-hello-java
#| lang: en
#| fig-cap: Tester l'application Java localement

./mvnw package -Pprod
java -jar target/hello-java-*.jar app.App1
```

::: {.callout-important title="A FAIRE"}
1. Ecrire un Dockerfile pour construire l’image Docker. Vous utiliserez l’image de base `eclipse-temurin:21-jdk-alpine`.
Vous devrez copier les fichiers, construire l’application avec Maven Wrapper, et définir la commande de démarrage.
2. Construire l’image Docker
3. Lancer un conteneur pour exécuter l’application Java.

:::

::: {.callout-tip collapse="true"}
## Solution Dockerfile

```{.dockerfile include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/Dockerfile"
    code-line-numbers="true"
    show-language="true"
    filename="Dockerfile" language-title="Dockerfile"
   }
```
:::

::: {.callout-tip collapse="true"}
## Solution Commandes

```{bash}
#| echo: true
#| output: true
docker build -t hello-java:0.0.1 .
```

```{bash}
#| echo: true
#| output: true
docker run --rm hello-java:0.0.1
```
:::


## Exercice 2 — `ARG`, `ENV` (adapté au projet `hello-java`)

Ajouter au projet la classe `app.App2` qui lit un fichier pour connaitre le commit. Ce fichier sera créé  à la construction de l'image avec un `build-arg`. L'application lit aussi la variable d'environnement `LOG_LEVEL` (pour contrôler le niveau de logs au runtime).

```{.java include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/src/main/java/app/App2.java"
    code-line-numbers="true"
    show-language="true"
    filename="src/main/java/app/App2.java"
    }
```

:::{.callout-important title="A FAIRE"}
Créer un `Dockerfile.arg_env` qui accepte un `build-arg` et crée le fichier `/app/GIT_COMMIT` avec cette valeur.
:::

Depuis le dossier `hello-java/`, construisez l'image en passant la valeur du commit au build :

```{bash}
#| echo: true
#| output: true
docker build \
    -f Dockerfile.arg_env \
    --build-arg GIT_COMMIT=abc123 \
    -t hello-java:env \
    .
```

1. Lancez le conteneur pour vérifier que le commit est bien passé au build :

```{bash}
#| echo: true
#| output: true
docker run --rm hello-java:env
```

2. Lancez le conteneur en surchargeant avec une variable d'environnement le niveau de logs (runtime) :

```{bash}
#| echo: true
#| output: true
docker run --rm --env LOG_LEVEL=DEBUG hello-java:env
```

3. Que peut-on faire si on ajoute dans le Dockerfile une instruction `ENV COMMIT=${GIT_COMMIT}` ?

:::{.callout-tip collapse="true"}
## Solution Dockerfile


```{.dockerfile include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/Dockerfile.arg_env"
    code-line-numbers="true"
    show-language="true"
    filename="Dockerfile.arg_env" language-title="Dockerfile"
   }
```
:::

## EXERCICE 3 — Multi-stage build + ENTRYPOINT/CMD

Compléter l'application Java :

```{.java include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/src/main/java/app/App3.java"
    code-line-numbers="true"
    show-language="true"
    filename="src/main/java/app/App3.java"
    }
```

::: {.callout-important title="A FAIRE"}
Créer une image optimisée en multi-stage et utiliser `ENTRYPOINT` + `CMD`.

1. Ecrire un Dockerfile `Dockerfile.multistage` qui utilise une construction en deux étapes :

   - une étape de build utilisant l’image `eclipse-temurin:21-jdk` pour compiler l’application Java
   - une étape runtime utilisant l’image `eclipse-temurin:21-jre` pour exécuter cette nouvelle classe de l’application Java
2. Utiliser `ENTRYPOINT` pour définir la commande principale et `CMD` pour passer un argument optionnel (le nom à saluer).

:::

Tester votre solution avec les commandes suivantes :

Construire l’image Docker

```{bash}
#| echo: true
#| output: true
docker build \
  --file Dockerfile.multistage \
  -t hello-java:0.0.1-multi \
  .
```

4. Lancer le conteneur sans argument (doit afficher "Hello World!")

```{bash}
#| echo: true
#| output: true
docker run --rm hello-java:0.0.1-multi
```

5. Lancer le conteneur avec un argument (doit afficher "Hello Pierre!")


```{bash}
#| echo: true
#| output: true
docker run --rm -e NAME=Pierre hello-java:0.0.1-multi
```

6. comparer la taille de l’image optimisée
```{bash}
#| echo: true
#| output: true
docker image ls --filter "reference=hello-java*"
```

:::{.callout-important title="Extensions (Bonus)"}
1. (Bonus 1) Modifier l’application Java pour ajouter une variable d’environnement `GREETING` personnalisable pour le message de salutation.
2. (Bonus 2) Optimiser le Dockerfile pour le pas télécharger inutilement des dépendances Maven à chaque build quand seule le code source change.
3. (Bonus 3) Utiliser `--mount=type=cache` associé à `RUN` pour cacher le répertoire `.m2` lors du build Maven.
4. (Bonus 4) Utiliser des images de base plus légères (ex: `eclipse-temurin:21-jre-alpine`).
:::

::: {.callout-tip collapse="true"}
## Solution Dockerfile


```{.dockerfile include="/home/jovyan/work/examples/github/ebpro/notebook-containers-intro-exercices-images/Dockerfile.multistage"
    code-line-numbers="true"
    show-language="true"
    filename="Dockerfile.multistage" language-title="Dockerfile"
   }
```

:::
