Alternative Image Builds
About
This lab will walk you through steps to build container images with various technologies.
Validation
Validate Java installation.
java --versionShould display output like (version might differ):
openjdk 17.0.9 2023-10-17
OpenJDK Runtime Environment (build 17.0.9+9-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.9+9-Ubuntu-120.04, mixed mode, sharing)Dockerfile Exercises
In this part of the exercise multiple options to use Dockerfiles will be shown and applied.
Set environment and build code
Download/clone the repo and change to the root folder:
git clone https://github.com/maeddes/options-galore-container-build.gitNote: Without git CLI you can download the repo as zip file here and extract it
Change your command line shell to the root folder.
cd options-galore-container-buildBuild the code:
Change to the Java sample app directory. Here you will find a small Spring Boot application to play with.
cd springbootBefore it is possible to put the compiled Java code into a container, the source code has to be build and packaged into a jar file. Maven is being used here to perform this step.
./mvnw clean packageThe download might take a bit as the fresh machine does not have any cache yet. After completion you can validate the build artifact (timestamps will of course be different)
You will likely see the following error message
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 16.867 s
[INFO] Finished at: 2025-02-26T12:45:23+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.13.0:compile (default-compile) on project simplecode: Fatal error compiling: error: release version 21 not supported -> [Help 1]Indicating that the java version of the application does not match the java version installed on the machine. To fix this error we have two options:
- Change the java version in the pom.xml to 17
- Install the java version 21 on the machine
Via nano pom.xml you can view and edit the applications configuration, but we will use apt to install the java
version on the local machine.
apt install openjdk-21-jdk-headless -yand verify with
novatec@swd-vm-0:~/options-galore-container-build/springboot$ java --version
openjdk 21.0.6 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-120.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-120.04.1, mixed mode, sharing)Now we can try to build the application again with ./mvnw clean package and hopefully get the following output:
[INFO] ----------------------------------------
[INFO] BUILD SUCCESS
[INFO] ----------------------------------------
[INFO] Total time: 21.377 s
[INFO] Finished at: 2025-02-26T13:05:53+01:00
[INFO] ----------------------------------------Now run
ls -ltr ./target/simplecode-0.0.1-SNAPSHOT.jarwhich should show a file with around 21MB of size.
-rw-rw-r-- 1 novatec novatec 21410769 Dec 1 10:50 ./target/simplecode-0.0.1-SNAPSHOT.jarClassic Dockerfile
Observe contents of Dockerfile-simple-ubuntu
cat Dockerfile-simple-ubuntuFROM ubuntu:22.04
RUN apt update
RUN apt install openjdk-21-jre-headless -y
COPY target/simplecode-0.0.1-SNAPSHOT.jar /opt/app.jar
CMD ["java","-jar","/opt/app.jar"]This Dockerfile uses a plain Ubuntu base and installs the Java Runtime and other dependencies on top of it. Here we have to adjust the java version as well to 17. After that the jar file, which has been packaged in the previous step is being copied into the container image.
Build the first image with this Dockerfile:
docker build -f Dockerfile-simple-ubuntu -t java-app:v-simple-ubuntu .In the next steps two other base images are being used.
Build images with these predefined base images:
docker build -f Dockerfile-simple-temurin -t java-app:v-simple-temurin .docker build -f Dockerfile-simple-ibm-semeru -t java-app:v-simple-ibm-semeru .Validate images in local repo
docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE
java-app v-simple-ibm-semeru eca3b4b4162e 6 seconds ago 301MB
java-app v-simple-temurin e716898751e9 26 seconds ago 294MB
java-app v-simple-ubuntu 8adb494eaee0 About a minute ago 404MBAs you can see there is a noticeable difference between the 3 java-app images in size. Observe the build history and differences of the 3 images:
docker history java-app:v-simple-ubuntu
docker history java-app:v-simple-temurin
docker history java-app:v-simple-ibm-semeruYou will observe different base layers and structure, but always the same top layer:
IMAGE CREATED CREATED BY SIZE COMMENT
eca3b4b4162e 45 seconds ago CMD ["java" "-jar" "/opt/app.jar"] 0B buildkit.dockerfile.v0
<missing> 45 seconds ago COPY target/simplecode-0.0.1-SNAPSHOT.jar /o… 21.4MB buildkit.dockerfile.v0Optional: Use tool “dive” to show detailed history of image:
dive java-app:v-simple-ubuntudive java-app:v-simple-temurindive java-app:v-simple-ibm-semeruThe dive tool acts like a command line browser for container images. Once it has loaded, you can navigate through the layers using arrow up and down keys. With tab you can switch to an individual layer and browse the directory/file-based content. Use Ctrl+M and Ctrl+U for filtering. Ctrl+C will quit the tool and return to the shell.
Multi-Stage
Build image with Multistage Dockerfile:
docker build -f Dockerfile-multistage-builder -t java-app:v-multistage-builder .This will take a while as all the maven dependencies need to be downloaded.
Validate history:
docker history java-app:v-multistage-builderExplore docker images:
docker imagesREPOSITORY TAG IMAGE ID CREATED SIZE
java-app v-multistage-builder 6c9ca2401261 12 seconds ago 294MBBuildKit
Build with multistage cache:
docker build -f Dockerfile-multistage-cache -t java-app:v-multistage-cache .Change the code and rebuild:
You can use an editor to change a method name in src/main/java/de/maeddes/simplecode/SimplecodeApplication.java or
simply execute
sed -i 's/hello/helloABC/g' src/main/java/de/maeddes/simplecode/SimplecodeApplication.javaRebuild and observe faster build through caching:
docker build -f Dockerfile-multistage-cache -t java-app:v-multistage-cache .Observe the history to validate that top layer is still ‘monolithic’:
docker history java-app:v-multistage-cacheBuild the code with a layered jar approach:
docker build -f Dockerfile-multistage-layered -t java-app:layered .Display the layered state:
docker history java-app:layeredIMAGE CREATED CREATED BY SIZE COMMENT
452290c47f57 21 seconds ago ENTRYPOINT ["java" "org.springframework.boot… 0B buildkit.dockerfile.v0
<missing> 21 seconds ago COPY application/application/ ./ # buildkit 6.51kB buildkit.dockerfile.v0
<missing> 23 seconds ago COPY application/snapshot-dependencies/ ./ #… 0B buildkit.dockerfile.v0
<missing> 29 seconds ago COPY application/spring-boot-loader/ ./ # bu… 239kB buildkit.dockerfile.v0
<missing> 31 seconds ago COPY application/dependencies/ ./ # buildkit 21.3MB buildkit.dockerfile.v0The modified application consumes only a few kB whereas the layer with dependencies will often stay constant.
Jib
Jib is an open-source project. Find info here . It is most commonly invoked via Maven or Gradle. As before Maven is being used in the example.
./mvnw compile com.google.cloud.tools:jib-maven-plugin:3.3.2:dockerBuild -Dimage=java-app:jibThis is all that needs to be done to build the image. Compilation, packaging and image build is all completed in one step.
Inspect the layered structure of the image:
docker history java-app:jibIMAGE CREATED CREATED BY SIZE COMMENT
a39f2e2b3651 N/A jib-maven-plugin:3.3.2 1.62kB jvm arg files
<missing> N/A jib-maven-plugin:3.3.2 2.66kB classes
<missing> N/A jib-maven-plugin:3.3.2 1B resources
<missing> N/A jib-maven-plugin:3.3.2 21.3MB dependenciesSimilar concepts as in the layered jar approach is applied here, too. Please note that Jib appears to have created the image at an unspecified time. This is for reproducibility purposes: Jib adds files and directories in a consistent order, and fakes creation and modification times and permissions for all files and directories. Jib also ensures that the image metadata is recorded in a consistent order, and that the container image has a faked creation time.
Cloud-native buildpacks
The following exercise use the technology of cloud-native buildpacks . This framework allows you to use so called “builders”, which have the role to perform the steps of language detection, code compilation and container build all in standardized, repeatable way.
Access the pack CLI and list the suggested builders
pack builder suggestFor the examples in this tutorial use the base builder image from Paketo buildpacks .
pack config default-builder paketobuildpacks/builder:baseBuild the container image using the buildpack. The first download will take quite a while as the builder image contains all available buildpacks.
pack build java-app:packThe first invocation will take a long time. The builder image is big as it contains all the logic plus buildpacks.
After the download the detection and compilation steps take place. Compared to Dockerfiles and Jib there is a pretty comprehensive output, which is called the “bill of materials”. This can also help you debugging, if things go wrong.
It should display an output like:
===> ANALYZING
...
===> DETECTING
...
===> RESTORING
===> BUILDING
...
===> EXPORTING
...
Successfully built image java-app:packPaketo with Spring Boot and Maven
In case of a Spring Boot application there is also an option to invoke the Paketo buildpacks from a Maven or Gradle call. In this case the compilation is being done with the local build tool and then handed off to the buildpack mechanism.
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=java-app:paketoSource-to-Image (OpenShift)
Source-to-Image (S2I) is a toolkit and workflow for building reproducible container images from source code. It is integrated into OpenShift .
S2I produces ready-to-run images by injecting source code into a container image and letting the container prepare that source code for execution. By creating self-assembling builder images, you can version and control your build environments exactly like you use container images to version your runtime environments.
Build the container image using s2i. The first run will take a while as all dependencies will need to be downloaded.
s2i build . registry.access.redhat.com/ubi8/openjdk-17 java-app:s2iI.e. build from the current directory, using an openjdk-17 image as base, and build into an image called java-app:s2i,
resulting in output like this:
INFO Performing Maven build in /tmp/src
[...]
INFO Copying deployments from target to /deployments...
'/tmp/src/target/simplecode-0.0.1-SNAPSHOT.jar' -> '/deployments/simplecode-0.0.1-SNAPSHOT.jar'
INFO Cleaning up source directory (/tmp/src)
Build completed successfullyThis is all that needs to be done to build the image. Compilation, packaging and image build is all completed in one step.
Inspect the layered structure of the image:
docker history java-app:s2iIMAGE CREATED CREATED BY SIZE COMMENT
471e8fe57e15 About a minute ago /bin/sh -c tar -C /tmp -xf - && /usr/local/s… 80.4MB
f5b3ec58f205 2 weeks ago /bin/sh -c #(nop) USER 185 348MB FROM registry.access.redhat.com/ubi8/ubi-minimal@sha256:b93deceb59a58588d5b16429fc47f98920f84740a1f2ed6454e33275f0701b59
<missing> 2 weeks ago /bin/sh -c mv -fZ /tmp/ubi.repo /etc/yum.rep… 0B
[...]The second layer and all those below (here marked as <missing>) belong to the specified build image
registry.access.redhat.com/ubi8/openjdk-17, cf. docker images registry.access.redhat.com/ubi8/openjdk-17.
Further runs can make use of s2i’s caching, though, via incremental builds.
s2i build . registry.access.redhat.com/ubi8/openjdk-17 java-app:s2i --incrementalYou’ll notice this run finishes much quicker as it can be built on top of the cached data from the previous run.







