Pratique Containers : Images Docker

Créer et optimiser des images Docker

Practice
Containers
Docker
Images
Exercices pratiques pour créer et optimiser des images Docker avec des applications Java simples.
Auteur
Affiliations

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2026-01-21

ImportantCorrections

La correction des exercices est disponible dans le dépôt GitHub https://github.com/ebpro/notebook-containers-intro-exercices-images.

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
src/main/java/app/App1.java
package app;

public class App1 {
    public static void main(String[] args) {
        System.out.println("Hello from Java in Docker!");
    }
}
hello-java/pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>app</groupId>
    <artifactId>hello-java</artifactId>
    <version>0.0.1</version>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

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

mvn wrapper:wrapper

Tester que l’application fonctionne localement :

./mvnw --quiet package
java -cp target/hello-java-*.jar app.App1
ImportantA 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.
# Définir l'image de base
FROM eclipse-temurin:21-jdk-alpine

# Definir le répertoire de travail
WORKDIR /app

# Copier les fichiers du projet dans le conteneur
COPY . .

# Construire l'application avec Maven Wrapper
RUN ./mvnw -q package

# Définir la commande de démarrage
CMD ["java", "-cp", "target/hello-java-*.jar", "app.App1"]
docker build -t hello-java:0.0.1 .
docker run --rm hello-java:0.0.1

Exercice 2 — ARG vs ENV (adapté au projet hello-java)

Ajouter au projet la classe app.App2 qui lit un fichier pour connaitre le commit utilisé à la construction et la variable LOG_LEVEL (pour contrôler le niveau de logs au runtime).

src/main/java/app/App2.java
package app;

import java.util.Optional;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

public class App3 {
    private static final Logger logger = LoggerFactory.getLogger(App3.class);

    public static void main(String[] args) {
        String name = Optional.ofNullable(System.getenv("NAME")).orElse("World");
        logger.info("Hello " + name + "!");
    }
}
ImportantA FAIRE

Créer un Dockerfile.arg_env pour montrer la différence entre une information fixe au build (ARG) et une configuration modifiable au runtime (ENV).

  1. Depuis le dossier hello-java/, construisez l’image en passant la valeur du commit au build :
docker build --quiet -f Dockerfile.arg_env --build-arg GIT_COMMIT=abc123 -t hello-java:env .
  1. Lancez le conteneur sans surcharge :
docker run --rm hello-java:env
  1. Lancez le conteneur en surchargeant le niveau de logs (runtime) :
docker run --rm -e LOG_LEVEL=DEBUG hello-java:env

comparez les résultats.

FROM eclipse-temurin:21-jdk

WORKDIR /app

# Définir une variable d’argument pour le commit Git
ARG GIT_COMMIT="unknown"

# Pour rendre la variable d’argument disponible en tant que variable d’environnement
# Il est alors possible de la surcharger au runtime avec --env
# ENV GIT_COMMIT=${GIT_COMMIT}

# inscrire la valeur dans un fichier dans l'image qui pourra être lue par l'application
RUN printf '%s' "${GIT_COMMIT}" > /app/GIT_COMMIT

# Ajouter un label avec le commit Git dans les métadonnées de l’image
LABEL git.commit="${GIT_COMMIT}"

# Configuration dynamique du conteneur
ENV LOG_LEVEL=INFO


COPY . .

RUN ./mvnw -q package

CMD ["java", "-cp", "target/hello-java-0.0.1.jar", "app.App2"]

EXERCICE 3 — Multi-stage build + ENTRYPOINT/CMD

ImportantA FAIRE

Créer une image optimisée en multi-stage et utiliser ENTRYPOINT + CMD.

Compléter l’application Java :

src/main/java/app/App3.java
package app;

import java.util.Optional;

public class App3 {
    public static void main(String[] args) {
        String name = Optional.ofNullable(System.getenv("NAME")).orElse("World");
        System.out.println("Hello " + name + "!");
    }
}
  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
docker build \
  --file Dockerfile.multistage -t hello-java:0.0.1-multi .

docker run --rm hello-java:0.0.1-multi
docker run --rm -e NAME=Pierre hello-java:0.0.1-multi

docker image ls | grep hello-java
  1. Utiliser ENTRYPOINT pour définir la commande principale et CMD pour passer un argument optionnel (le nom à saluer).
  2. Construire l’image Docker
  3. Lancer le conteneur sans argument (doit afficher “Hello World!”)
  4. Lancer le conteneur avec l’argument NAME=Pierre (doit afficher “Hello Pierre!”)
ImportantExtensions (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).
Dockerfile.multistage
# Build stage
FROM eclipse-temurin:21-jdk AS build

WORKDIR /app
COPY . .
RUN ./mvnw -q package

# Runtime stage
FROM eclipse-temurin:21-jre

WORKDIR /app
COPY --from=build /app/target/classes /app/target/classes

ENV NAME=World

ENTRYPOINT ["java", "-cp", "target/classes", "app.App"]
CMD []

Réutilisation