dannail
dannail

Reputation: 455

How to build a Docker image for Spring Boot application without the JAR at hand

I have followed these tutorials to build Docker image for my Spring Boot application, which uses Maven as build tool. I am using boot2docker VM on top of Windows 10 machine, cloning my project to the VM from my Bitbucker repository.

https://spring.io/guides/gs/spring-boot-docker/

https://www.callicoder.com/spring-boot-docker-example/

I understand the instructions told, but, I failed to build a proper Docker image. Here's the things I tried.

  1. Use the Spotify maven plugin for Dockerfile. Try to run ./mvnw to build the JAR as well as the Docker image. But, I don't have Java installed in the boot2docker. So the Maven wrapper ./mvnw cannot be run.

  2. I tried to build the JAR through Dockerfile, which is based on the openjdk:8-jdk-alpine image. I added RUN ./mvnw package instruction in the Dockerfile. Then run docker build -t <my_project> . to build Docker image. It fails at the RUN instruction, claiming /bin/sh: mvnw: not found The command '/bin/sh -c mvnw package' returned a non-zero code: 127

My Dockerfile, located in the directory where the mvnw is located:

MAINTAINER myname

VOLUME /tmp

RUN ./mvnw package

ARG JAR_FILE=target/myproject-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

For 1, I need to have Java installed in the OS where the Docker engine resides. But I think it's not a good practice coz this lowers the portability.

For 2, first, I don't know how to run ./mvnw successfully in Dockerfile. Second, I'm not sure if it is a good practice to build the Spring Boot JAR through Dockerfile, coz I don't see any "Docker for Spring Boot" tutorial to tell to do so.

So, what is the best practice to solve my situation? I'm new to Docker. Comments and answers are appreciated!

Upvotes: 2

Views: 7414

Answers (4)

Javad Mahdioun
Javad Mahdioun

Reputation: 321

  • Make sure your pom.xml contains the following finalName enter image description here

  • Make sure the versions used are compatible** enter image description here

  • remove the target directory (mvn clean) enter image description here

here is the Dockerfile

FROM maven:3.8.4-openjdk-17-slim AS builder
COPY pom.xml /app/
COPY src /app/src
RUN mvn -f /app/pom.xml clean package

FROM openjdk:17-jdk-alpine
COPY --from=builder /app/target/*.jar /app/msg-server.jar
ENTRYPOINT ["java", "-jar", "/app/msg-server.jar"]

enter image description here

Upvotes: 1

Sanjay Bharwani
Sanjay Bharwani

Reputation: 4739

My solution is based on gradle. I am using GradleWrapper.

So below command will build the project and generate the springboot executable jar in the build/libs folder

./gradlew clean build

And below is the code of Dockerfile

FROM bellsoft/liberica-openjdk-alpine:17
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

This basically starts on the java17 image, copies the jar from build directory to app.jar and then register the ENTRYPOINT to run the command java -jar /app.jar

Tip: You may need to add below task in build.gradle, otherwise by default it will generate the sources file as well, which will make copy command fail as it will find more than one jar file.

jar {
    enabled = false
}

Upvotes: 0

Uyric
Uyric

Reputation: 744

From your second example I think you are misunderstanding how Docker builds images. When Docker executes RUN ./mvnw package, the file mvnw must exist in the file system of the image being built, which means you should have an instruction like COPY mvnw . in a previous step - that will copy the file from your local filesystem into the image.

You will likely need to copy the entire project structure inside the image, before calling ./mvnw, as the response from @BMitch suggests.

Also, as @BMitch said, to generate a small-sized image it's normally recommended to use a multi-stage build, where the first stage installs every dependency but the final image has only your JAR.

You could try something like below:

# First stage: build fat JAR

# Select base image.
# (The "AS builder" gives a name to the stage that we will need later)
# (I think it's better to use a slim image with Maven already installed instead
#  than ./mvnw. Otherwise you could need to give execution rights to your file
#  with instructions like "RUN chmod +x mvnw".)
FROM maven:3.6.3-openjdk-8-slim AS builder

# Set your preferred working directory
# (This tells the image what the "current" directory is for the rest of the build)
WORKDIR /opt/app

# Copy everything from you current local directory into the working directory of the image
COPY . .

# Compile, test and package
# (-e gives more information in case of errors)
# (I prefer to also run unit tests at this point. This may not be possible if your tests
#  depend on other technologies that you don't whish to install at this point.)
RUN mvn -e clean verify

###

# Second stage: final image containing only WAR files

# The base image for the final result can be as small as Alpine with a JRE
FROM openjdk:8-jre-alpine

# Once again, the current directory as seen by your image
WORKDIR /opt/app

# Get artifacts from the previous stage and copy them to the new image.
# (If you are confident the only JAR in "target/" is your package, you could NOT
#  use the full name of the JAR and instead something like "*.jar", to avoid updating
#  the Dockerfile when the version of your project changes.)
COPY --from=builder /opt/app/target/*.jar ./

# Expose whichever port you use in the Spring application
EXPOSE 8080

# Define the application to run when the Docker container is created.
# Either ENTRYPOINT or CMD.
# (Optionally, you could define a file "entrypoint.sh" that can have a more complex
#  startup logic.)
# (Setting "java.security.egd" when running Spring applications is good for security
#  reasons.)
ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -jar /opt/app/*.war

Upvotes: 0

BMitch
BMitch

Reputation: 263469

You can install maven and run the compile directly in the build. Typically this would be a multi-stage build to avoid including the entire jdk in your pushed image:

FROM openjdk:8-jdk-alpine as build
RUN apk add --no-cache maven
WORKDIR /java
COPY . /java
RUN mvn package -Dmaven.test.skip=true
EXPOSE 8080
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/java/target/myproject-0.0.1-SNAPSHOT.jar"]

The above is a stripped down version of a rework from the same example that I've done in the past. You may need to adjust filenames in your entrypoint, but the key steps are to install maven and run it inside your build.

Upvotes: 2

Related Questions