Pratique Containers : Images Docker
Créer et optimiser des images Docker
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:wrapperTester que l’application fonctionne localement :
./mvnw --quiet package
java -cp target/hello-java-*.jar app.App1- 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. - Construire l’image Docker
- 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.1Exercice 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 + "!");
}
}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).
- 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 .- Lancez le conteneur sans surcharge :
docker run --rm hello-java:env- Lancez le conteneur en surchargeant le niveau de logs (runtime) :
docker run --rm -e LOG_LEVEL=DEBUG hello-java:envcomparez les résultats.
hello-java)
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
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 + "!");
}
}Ecrire un Dockerfile
Dockerfile.multistagequi utilise une construction en deux étapes :- une étape de build utilisant l’image
eclipse-temurin:21-jdkpour compiler l’application Java - une étape runtime utilisant l’image
eclipse-temurin:21-jrepour exécuter cette nouvelle classe de l’application Java
- une étape de build utilisant l’image
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- Utiliser
ENTRYPOINTpour définir la commande principale etCMDpour passer un argument optionnel (le nom à saluer). - Construire l’image Docker
- Lancer le conteneur sans argument (doit afficher “Hello World!”)
- Lancer le conteneur avec l’argument
NAME=Pierre(doit afficher “Hello Pierre!”)
- (Bonus 1) Modifier l’application Java pour ajouter une variable d’environnement
GREETINGpersonnalisable pour le message de salutation. - (Bonus 2) Optimiser le Dockerfile pour le pas télécharger inutilement des dépendances Maven à chaque build quand seule le code source change.
- (Bonus 3) Utiliser
--mount=type=cacheassocié àRUNpour cacher le répertoire.m2lors du build Maven. - (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 []