A year ago Dave Syer posted an excellent, comprehensive overview of how to run Spring Boot applications in Docker. He delves into the various ways to package Spring Boot applications into properly layered Docker images, highlighting the benefits and basic building blocks of each approach.

This post expands on an hint within the corresponding Spring Boot Docker topical guide:

You could also copy the Spring Boot fat JarLauncher into the image and use it to run the app - it would work and you wouldn’t need to specify the main class.

— Dave Syer
Spring Boot Docker topical guide

Since the guide leaves these details to the reader, we have had to work out the details ourselves. Luckily it isn’t hard; it comes down to extracting the Spring Boot fat jar contents, and adding the components in separate layers.

Uncompress the fat jar

While it could have been easy enough to run jar -xf foo-service-0.0.1-SNAPSHOT.jar on our CI nodes, that would have required impractical build pipelines alterations. In our case we were looking for a way to decompress the jar file as part of our Apach Maven builds, so we chose to do the same within a Maven profile in our shared parent pom.xml:

<profiles>
  <profile>
    <id>unpacked</id>
    <activation>
      <file>
        <!-- Skips parent pom projects -->
        <exists>src/main/java</exists>
      </file>
    </activation>
    <build>
      <plugins>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>exec-maven-plugin</artifactId>
          <executions>
            <execution>
              <id>unpack-fatjar</id>
              <phase>verify</phase>
              <goals>
                <goal>exec</goal>
              </goals>
              <configuration>
                <workingDirectory>${project.build.directory}/dependency/</workingDirectory>
                <executable>jar</executable>
                <arguments>
                  <argument>-xf</argument>
                  <argument>../${project.build.finalName}.${project.packaging}</argument>
                </arguments>
              </configuration>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
  </profile>
</profiles>

This automatically decompresses any jar file when running mvn verify; enabling us to copy the contents separately in the next step.

Create layered Docker image

Expanding on the not-yet Multi-Stage Build Dockerfile from the original blogpost above, we now only have to add the JarLauncher and alter the CMD instructions.

FROM openjdk:11.0.4-jre-slim
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/BOOT-INF/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/org /app/org
COPY ${DEPENDENCY}/BOOT-INF/classes /app/BOOT-INF/classes
CMD [ "java", "-XX:MaxRAMPercentage=80", "org.springframework.boot.loader.JarLauncher" ]

What I like about this approach is that you end up with properly layered Docker images, without specific knowledge of the packaged application in terms of copied files or CMD instruction. While personally I have a preference for tools like Jib, this approach fit in best at two recent assignments.

shadow-left