[Rust - Docker] Docker build and run Java Spring Boot Web app with multi-stage builds

Java Spring Boot Docker with multiple-stage builds

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

Multistage builds are useful to anyone who has struggled to optimize Dockerfiles while keeping them easy to read and maintain. See Use multi-stage builds | Docker Documentation - https://docs.docker.com/develop/develop-images/multistage-build/ to learn more.

Prerequites

Docker

Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.

See Get Docker | Docker Documentation - https://docs.docker.com/get-docker/ to learn more.

Initialize

Use start.spring.io - https://start.spring.io/ to create a “col-java-docker-example” project. In the “Dependencies” dialog search for and add the “web” dependency as shown in the screenshot. Hit the “Generate” button, download the zip, and unpack it into a folder on your computer.

See Spring | Spring Quickstart Guide - https://spring.io/quickstart to learn more.

Mininal Image

Image Size Comment
Builder gradle:7.3.3-jdk17-alpine ~560MB [Gradle - Official Image
Final eclipse-temurin:17.0.1_12-jre-alpine 47.47 MB~146MB [Eclipse-temurin - Official Image
Final gcr.io/distroless/java17-debian11 ~230MB GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system. - https://github.com/GoogleContainerTools/distroless
Final openjdk:17.0.1-slim-bullseye ~407MB [Openjdk - Official Image

Build

Builder Image

First, build the builder image with Dockerfile-base file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# Dockerfile-base

################################################################
#
# Arguments
#
################################################################

# Gradle - Official Image | Docker Hub
# https://hub.docker.com/_/gradle
# ~560MB
# ARG BUILDER_IMAGE=gradle:7.3.3-jdk17-alpine

################################################################
#
# Builder Image
#
################################################################

# docker build . -f Dockerfile-base -t cloudolife/base-col-java-docker-example
FROM ${BUILDER_IMAGE} as Builder

LABEL maintainer="Benjamin CloudoLife <benjamin [{@}] cloudolife.com"

ARG WORK_DIR=/app
WORKDIR ${WORK_DIR}

COPY . .

# Building Java Libraries Sample
# https://docs.gradle.org/current/samples/sample_building_java_libraries.html
RUN gradle build
# # Excelude test
# RUN gradle build -x test

Build builder image:

1
$ docker build . -f Dockerfile-base -t cloudolife/base-col-java-docker-example

Final Image

Then, build the final image with Dockerfile-final file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# Dockerfile-final

################################################################
#
# Arguments
#
################################################################

ARG BUILDER_IMAGE=cloudolife/base-col-java-docker-example

# Openjdk - Official Image | Docker Hub
# https://hub.docker.com/_/openjdk
# ~407MB
# ARG FINAL_IMAGE=openjdk:17.0.1-slim-bullseye

# ~471MB
# ARG FINAL_IMAGE=openjdk:17.0.1

# GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.
# https://github.com/GoogleContainerTools/distroless
# ~230MB
ARG FINAL_IMAGE=gcr.io/distroless/java17-debian11

# Tomcat - Official Image | Docker Hub
# https://hub.docker.com/_/tomcat
# 111.43 MB~
# ARG FINAL_IMAGE=tomcat:9.0.56-jre17-temurin-focal

# Eclipse-temurin - Official Image | Docker Hub
# https://hub.docker.com/_/eclipse-temurin
# 47.47 MB~146MB
# ARG FINAL_IMAGE=eclipse-temurin:17.0.1_12-jre-alpine


################################################################
#
# Final Image
#
################################################################

# docker build . -t cloudolife/col-java-docker-example
FROM ${FINAL_IMAGE} as Final

LABEL maintainer="Benjamin CloudoLife <benjamin [{@}] cloudolife.com"

ARG WORK_DIR=/app
WORKDIR ${WORK_DIR}

ARG JAR_FILE=col-java-docker-example-0.0.1-SNAPSHOT.jar

# COPY ${JAR_FILE} app.jar
COPY --from=Builder ${WORK_DIR}/build/libs/${JAR_FILE} app.jar

COPY src/main/resources/application.properties .

EXPOSE 8080

ENTRYPOINT ["java","-jar","/app/app.jar", "--spring.config.location=/app/application.properties"]

Complete Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

################################################################
#
# Arguments
#
################################################################

# Gradle - Official Image | Docker Hub
# https://hub.docker.com/_/gradle
# ~560MB
ARG BUILDER_IMAGE=gradle:7.3.3-jdk17-alpine
# ARG BUILDER_IMAGE=cloudolife/base-col-java-docker-example

# Openjdk - Official Image | Docker Hub
# https://hub.docker.com/_/openjdk
# ~407MB
# ARG FINAL_IMAGE=openjdk:17.0.1-slim-bullseye

# ~471MB
# ARG FINAL_IMAGE=openjdk:17.0.1

# GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system.
# https://github.com/GoogleContainerTools/distroless
# ~230MB
ARG FINAL_IMAGE=gcr.io/distroless/java17-debian11

# Tomcat - Official Image | Docker Hub
# https://hub.docker.com/_/tomcat
# 111.43 MB~
# ARG FINAL_IMAGE=tomcat:9.0.56-jre17-temurin-focal

# Eclipse-temurin - Official Image | Docker Hub
# https://hub.docker.com/_/eclipse-temurin
# 47.47 MB~146MB
# ARG FINAL_IMAGE=eclipse-temurin:17.0.1_12-jre-alpine


################################################################
#
# Builder Image
#
################################################################

# docker build . -t cloudolife/base-col-java-docker-example --target Builder
FROM ${BUILDER_IMAGE} as Builder

LABEL maintainer="Benjamin CloudoLife <benjamin [{@}] cloudolife.com"

ARG WORK_DIR=/app
WORKDIR ${WORK_DIR}

COPY . .

# Building Java Libraries Sample
# https://docs.gradle.org/current/samples/sample_building_java_libraries.html
RUN gradle build
# # Excelude test
# RUN gradle build -x test


################################################################
#
# Final Image
#
################################################################

# docker build . -t cloudolife/col-java-docker-example
FROM ${FINAL_IMAGE} as Final

LABEL maintainer="Benjamin CloudoLife <benjamin [{@}] cloudolife.com"

ARG WORK_DIR=/app
WORKDIR ${WORK_DIR}

ARG JAR_FILE=col-java-docker-example-0.0.1-SNAPSHOT.jar

# COPY ${JAR_FILE} app.jar
COPY --from=Builder ${WORK_DIR}/build/libs/${JAR_FILE} app.jar

COPY src/main/resources/application.properties .

EXPOSE 8080

ENTRYPOINT ["java","-jar","/app/app.jar", "--spring.config.location=/app/application.properties"]

# docker run -it --rm --name col-java-docker-example -p 8080:8080 cloudolife/col-java-docker-example

Build builder image:

1
$ docker build . -t cloudolife/base-col-java-docker-example --target Builder
1
2
3
4
5
# Dockerfile

- ARG BUILDER_IMAGE=gradle:7.3.3-jdk17-alpine
+ # ARG BUILDER_IMAGE=gradle:7.3.3-jdk17-alpine
+ ARG BUILDER_IMAGE=cloudolife/base-col-java-docker-example

Build builder image:

1
$ docker build . -t cloudolife/col-java-docker-example

Run

Run the final image:

1
$ docker run -it --rm --name col-java-docker-example -p 8080:8080 cloudolife/col-java-docker-example

Open your browser to visit http://localhost:8080.

Check

Check image size:

1
2
3
4
$ docker images | grep java
cloudolife/col-java-docker-example latest cba7b22b0bd0 6 minutes ago 271MB
cloudolife/base-col-java-docker-example latest 308690197941 50 minutes ago 792MB
gcr.io/distroless/java17-debian11 latest f94fc2bdbc22 52 years ago 230MB

FAQs

org.apache.tomcat.jni.LibraryNotFoundError: Can’t load library: /app/bin/libtcnative-1.so, Can’t load library: /app/bin/liblibtcnative-1.so, no tcnative-1

1
org.apache.tomcat.jni.LibraryNotFoundError: Can't load library: /app/bin/libtcnative-1.so, Can't load library: /app/bin/liblibtcnative-1.so, no tcnative-1 in java.library.path: /opt/java/openjdk/lib/server:/opt/java/openjdk/lib:/opt/java/openjdk/../lib:/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib, no libtcnative-1 in java.library.path: /opt/java/openjdk/lib/server:/opt/java/openjdk/lib:/opt/java/openjdk/../lib:/usr/java/packages/lib:/usr/lib64:/lib64:/lib:/usr/lib

TODO.

See Solves Springboot `APR-based Apache Tomcat Native Library’ under Linux - https://www.fatalerrors.org/a/springboot-solves-apr-based-apache-tomcat-native-library-tips-under-linux.html to learn more.

References

[1] Spring | Spring Quickstart Guide - https://spring.io/quickstart

[2] Get Docker | Docker Documentation - https://docs.docker.com/get-docker/

[3] Use multi-stage builds | Docker Documentation - https://docs.docker.com/develop/develop-images/multistage-build/

[4] Dockerfile reference | Docker Documentation - https://docs.docker.com/engine/reference/builder/

[5] Gradle - Official Image | Docker Hub - https://hub.docker.com/_/gradle

[6] Openjdk - Official Image | Docker Hub - https://hub.docker.com/_/openjdk

[7] Tomcat - Official Image | Docker Hub - https://hub.docker.com/_/tomcat

[8] Eclipse-temurin - Official Image | Docker Hub - https://hub.docker.com/_/eclipse-temurin

[9] GoogleContainerTools/distroless: 🥑 Language focused docker images, minus the operating system. - https://github.com/GoogleContainerTools/distroless

[10] Java | Oracle - https://www.java.com/en/

[11] Spring Boot - https://spring.io/projects/spring-boot