Faster Java containers with Docker multi-stage builds

Docker’s multi-stage build feature means Java containers can be smaller and more efficient.

Build smaller Docker Java images with multi-stage builds. Image from Docker blog.

Background

At MailSlurp we deploy Kotlin/Java Spring APIs in Docker containers to a Kubernetes cluster. Our API gives you unlimited private email addresses to test your app with.

Our containers are built with CircleCI. Recently we started to notice that containers were taking a long time to build and upload to our registry. Some were taking several minutes to upload and were up to 500mb in size.

We found out that the large size was because we copied all our source files, resources, and tests into the container in order to test and build it. We then shipped these containers straight to ECR.

Our Dockerfiles generally looked like this:

FROM openjdk:8-jdk as build

VOLUME /tmp
WORKDIR /
ADD . .

RUN ./gradlew --stacktrace clean build -x test
EXPOSE 5000ENTRYPOINT ["sh","-c","java -jar /app.jar"]

A better way

After some refactoring we were able to reduce our container size 10x to a much more reasonable 50mb. This decreased upload times and made our deployments faster.

We did this with Docker multi-stage builds; a feature that let’s you build an app in one container and then copy artifacts to another, slimmer container for execution.

Here is an example:

# build stage build the jar with all our resources
FROM openjdk:8-jdk as build

ARG VERSION
ARG JAR_PATH

VOLUME /tmp
WORKDIR /
ADD . .

RUN ./gradlew --stacktrace clean test build
RUN mv /$JAR_PATH /app.jar

# package stage
FROM openjdk:8-jdk-alpine
WORKDIR /
# copy only the built jar and nothing else
COPY --from=build /app.jar /

ENV VERSION=$VERSION
ENV JAVA_OPTS=-Dspring.profiles.active=production

EXPOSE 5000

ENTRYPOINT ["sh","-c","java -jar -Dspring.profiles.active=production /app.jar"]

As you can see, the first build stage copies all our project files into a java container runs tests and builds a jar. We then copy only this jar into a small alpine-linux container and push this final container to ECR. That way we are not bundling our test-suite and resource files in our production container!

There you have it. The same approach should work for any framework.

--

--

MailSlurp | Email APIs for developers

Test Email API for end-to-end with real email addresses. Support for NodeJS, PHP, Python, Ruby, Java, C# and more. See https://www.mailslurp.com for details.