Alternative Image Builds

Images Images

About

This lab will walk you through steps to build container images with various technologies.

Validation

Validate Java installation.

java --version

Should 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.git

Note: 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-build

Build the code:

Change to the Java sample app directory. Here you will find a small Spring Boot application to play with.

cd springboot

Before 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 package

The 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:

  1. Change the java version in the pom.xml to 17
  2. 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 -y

and 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.jar

which 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.jar

Classic Dockerfile

Image Image

Observe contents of Dockerfile-simple-ubuntu

cat Dockerfile-simple-ubuntu
FROM 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 images
REPOSITORY                                     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   404MB

As 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-semeru

You 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.v0

Optional: Use tool “dive” to show detailed history of image:

dive java-app:v-simple-ubuntu
dive java-app:v-simple-temurin
dive java-app:v-simple-ibm-semeru

The 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

Image Image

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-builder

Explore docker images:

docker images
REPOSITORY   TAG                    IMAGE ID       CREATED          SIZE
java-app     v-multistage-builder   6c9ca2401261   12 seconds ago   294MB

BuildKit

Build with multistage cache:

Image Image

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.java

Rebuild 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-cache

Build the code with a layered jar approach:

Image Image

docker build -f Dockerfile-multistage-layered -t java-app:layered .

Display the layered state:

docker history java-app:layered
IMAGE          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.v0

The modified application consumes only a few kB whereas the layer with dependencies will often stay constant.

Jib

Image Image

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:jib

This 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:jib
IMAGE          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    dependencies

Similar 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

Image Image

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 suggest

For the examples in this tutorial use the base builder image from Paketo buildpacks .

pack config default-builder paketobuildpacks/builder:base

Build 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:pack

The 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:pack

Paketo 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.

Image Image

./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=java-app:paketo

Source-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:s2i

I.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 successfully

This 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:s2i
IMAGE          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 --incremental

You’ll notice this run finishes much quicker as it can be built on top of the cached data from the previous run.