DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Coding
  3. Frameworks
  4. Creating Dual Layer Docker Images for Spring Boot Apps

Creating Dual Layer Docker Images for Spring Boot Apps

Want to learn more about optimizing Spring Boot apps for Docker? Check out this post on creating dual layer Docker images for Spring Boot applications.

Michael Thompson user avatar by
Michael Thompson
·
Sep. 25, 18 · Tutorial
Like (4)
Save
Tweet
Share
11.87K Views

Join the DZone community and get the full member experience.

Join For Free

In the first part of this series on Optimizing Spring Boot Apps for Docker, we looked at the single layer approach to building Docker images for Spring Boot applications and the implications it has for CI/CD pipelines. I proposed that a dual layer approach has concrete benefits over the single layer approach and that these benefits are in the form of efficiencies in iterative development environments.

Here, we introduce an approach to creating dual layer Docker images for existing Spring Boot applications using a new tool in Open Liberty called springBootUtility. There are alternate approaches to creating multi-layered Docker images for Spring Boot applications[1], but this approach focuses on creating a dual layer image from the existing application rather than altering a Maven or Gradle build step.

Dual Layer Approach

In the dual layer approach, we structure the Docker image so that the library dependencies of the Spring Boot app exist in a layer below the application code. By pushing the infrequently changing library dependencies down into a separate layer and keeping only the application classes in the top layer, iterative rebuilds and re-deployments are much faster.

blog optimizing spring boot dual layer

In order to accomplish this, we need a way to split the Spring Boot application into these separate components. First, you need to enter the springBootUtility.

The springBootUtility

The springBootUtility is a new tool in Open Liberty that will split the Spring Boot application into two parts: the library dependencies, such as the Spring Boot starters and other third-party libraries, and the application code. The library dependencies are placed in a library cache and the application code is used to construct a thin application. The thin app contains a file that references the libraries it needs on the classpath. This thin app can then be deployed to Open Liberty, which will generate the full classpath from the library cache.

Docker Multi-Stage Builds

The Dockerfile to build this dual layer image uses multi-stage builds. Multi-stage builds allow a single Dockerfile to create multiple images, where the contents of one image can be copied into another, discarding the temporary content. This allows you to drastically reduce the size of your final image, without needing to involve multiple Docker files. We use this function to split the Spring Boot application within the Docker build process.

The Docker Image

The Docker image uses Open JDK with Open J9 and Open Liberty. Open JDK provides a solid foundation of open-source Java technology. Open J9 brings along some performance improvements over the default Java Virtual Machine included with Open JDK. Open Liberty is a multi-programming model runtime, supporting Java EE, MicroProfile, and Spring. This allows development teams to use a variety of programming models with a consistent runtime stack.

Show Me the Code!

Let's take a look at the Dockerfile in all its gory glory (we’ll walk through what it does next):

FROM adoptopenjdk/openjdk8-openj9 as staging

ARG JAR_FILE
ENV SPRING_BOOT_VERSION 2.0

# Install unzip; needed to unzip Open Liberty
RUN apt-get update \
    && apt-get install -y --no-install-recommends unzip \
    && rm -rf /var/lib/apt/lists/*

# Install Open Liberty
ENV LIBERTY_SHA 4170e609e1e4189e75a57bcc0e65a972e9c9ef6e
ENV LIBERTY_URL https://public.dhe.ibm.com/ibmdl/export/pub/software/openliberty/runtime/release/2018-06-19_0502/openliberty-18.0.0.2.zip

RUN curl -sL "$LIBERTY_URL" -o /tmp/wlp.zip \
   && echo "$LIBERTY_SHA  /tmp/wlp.zip" > /tmp/wlp.zip.sha1 \
   && sha1sum -c /tmp/wlp.zip.sha1 \
   && mkdir /opt/ol \
   && unzip -q /tmp/wlp.zip -d /opt/ol \
   && rm /tmp/wlp.zip \
   && rm /tmp/wlp.zip.sha1 \
   && mkdir -p /opt/ol/wlp/usr/servers/springServer/ \
   && echo spring.boot.version="$SPRING_BOOT_VERSION" > /opt/ol/wlp/usr/servers/springServer/bootstrap.properties \
   && echo \
'<?xml version="1.0" encoding="UTF-8"?> \
<server description="Spring Boot Server"> \
  <featureManager> \
    <feature>jsp-2.3</feature> \
    <feature>transportSecurity-1.0</feature> \
    <feature>websocket-1.1</feature> \
    <feature>springBoot-${spring.boot.version}</feature> \
  </featureManager> \
  <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" httpsPort="9443" /> \
  <include location="appconfig.xml"/> \
</server>' > /opt/ol/wlp/usr/servers/springServer/server.xml \
   && /opt/ol/wlp/bin/server start springServer \
   && /opt/ol/wlp/bin/server stop springServer \
   && echo \
'<?xml version="1.0" encoding="UTF-8"?> \
<server description="Spring Boot application config"> \
  <springBootApplication location="app" name="Spring Boot application" /> \
</server>' > /opt/ol/wlp/usr/servers/springServer/appconfig.xml

# Stage the fat JAR
COPY ${JAR_FILE} /staging/myFatApp.jar

# Thin the fat application; stage the thin app output and the library cache
RUN /opt/ol/wlp/bin/springBootUtility thin \
 --sourceAppPath=/staging/myFatApp.jar \
 --targetThinAppPath=/staging/myThinApp.jar \
 --targetLibCachePath=/staging/lib.index.cache

# unzip thin app to avoid cache changes for new JAR
RUN mkdir /staging/myThinApp \
   && unzip -q /staging/myThinApp.jar -d /staging/myThinApp

# Final stage, only copying the liberty installation (includes primed caches)
# and the lib.index.cache and thin application
FROM adoptopenjdk/openjdk8-openj9

VOLUME /tmp

# Create the individual layers
COPY --from=staging /opt/ol/wlp /opt/ol/wlp
COPY --from=staging /staging/lib.index.cache /opt/ol/wlp/usr/shared/resources/lib.index.cache
COPY --from=staging /staging/myThinApp /opt/ol/wlp/usr/servers/springServer/apps/app

# Start the app on port 9080
EXPOSE 9080
CMD ["/opt/ol/wlp/bin/server", "run", "springServer"]

The Details

Using Docker's multi-stage build and the springBootUtility in Open Liberty, the Dockerfile splits the Spring Boot application.

We start with a staging image. First, we install unzip. Next, we download Open Liberty and stage in some configuration. All of this prep work is needed to get the Open Liberty tool in place. We know its pretty ugly, that's one of the things we'll be improving in the very near future when Liberty 18.0.0.2 Docker images are published.

Once the image has all of the tools it needs, the JAR file is copied into the staging image and split. After the thin app is created under /staging/myFatApp.jar, a further optimization step is taken to unzip it. This unzip causes the application to be hosted directly from the class files. This allows subsequent rebuilds to re-use the application layer if the class files have not changed.

Now that the staging work is done, we start fresh so that we can copy over the final Liberty installation, dependent libraries, and the thin application. The separate COPY commands in the Dockerfile generate the separate layers. The larger library dependency layer (34.2MB) and the smaller application layer (1.01MB) are what is meant by 'dual layer.'

$ docker history openlibertyio/spring-petclinic
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
883ee6374f66        7 minutes ago       /bin/sh -c #(nop)  CMD ["/opt/ol/wlp/bin/ser...   0B
e3ba1351fc05        7 minutes ago       /bin/sh -c #(nop)  EXPOSE 9080                  0B
86c646de6626        7 minutes ago       /bin/sh -c #(nop) COPY dir:589967d5ae0ade9a5...   1.01MB
8f98ce0a6c10        7 minutes ago       /bin/sh -c #(nop) COPY dir:d764c6a82219ed564...   34.2MB
240306c081cd        7 minutes ago       /bin/sh -c #(nop) COPY dir:0b45938a62d056d88...   200MB
161006b94f8e        22 minutes ago      /bin/sh -c #(nop)  VOLUME [/tmp]                0B
f50ba84462ab        3 weeks ago         /bin/sh -c #(nop)  ENV PATH=/opt/java/openjd...   0B
<missing>           3 weeks ago         /bin/sh -c set -eux;     ARCH="$(dpkg --prin...   193MB
<missing>           3 weeks ago         /bin/sh -c #(nop)  ENV JAVA_VERSION=jdk8u162...   0B
<missing>           3 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/* && ap...   16MB
<missing>           3 weeks ago         /bin/sh -c #(nop)  MAINTAINER Dinakar Gunigu...   0B
<missing>           2 months ago        /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           2 months ago        /bin/sh -c mkdir -p /run/systemd && echo 'do...   7B
<missing>           2 months ago        /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$...   2.76kB
<missing>           2 months ago        /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           2 months ago        /bin/sh -c set -xe   && echo '#!/bin/sh' > /...   745B
<missing>           2 months ago        /bin/sh -c #(nop) ADD file:592c2540de1c70763...   113MB


Now, when application changes are made, only the application layer needs to be changed.

Try it Out!

You can copy this Dockerfile and run it on your own clone of Pet Clinic.

$ docker build --build-arg JAR_FILE=target/spring-petclinic-2.0.0.BUILD-SNAPSHOT.jar -t openlibertyio/spring-petclinic .

The resulting Docker image looks like this:

blog optimizing spring boot dual layer with liberty

You will notice that the entire Docker image isn’t as small as the single layer approach. The base image is not based on Alpine Linux and Liberty’s installation is not minified. We’re working on improving that.

Future Steps

We’re happy with what we’ve built so far. However, to be honest, the user experience of building these images isn’t great. It can be done better, and we’ll be working on that in the coming months. We’ll also be publishing Docker images that contain a pre-configured Open Liberty instance. That will significantly reduce the complexity of the Dockerfile.

We also recognize that there is room for improvement when integrating these dual layer builds in a continuous delivery pipeline. That’s another aspect of improving the Spring Boot experience for Docker we’re interested in solving.

Lastly, this approach of splitting out static library dependencies from the application is not exclusive to Spring Boot applications! Similar efficiencies can also be gained with Java EE or MicroProfile applications. That’s another area we’re exploring.

References

[1] https://github.com/gclayburg/dockerPreparePlugin

Spring Framework Spring Boot Docker (software) app application

Published at DZone with permission of Michael Thompson, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Monolithic First
  • Building Microservice in Golang
  • The 5 Books You Absolutely Must Read as an Engineering Manager
  • Introduction to Spring Cloud Kubernetes

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: