2
votes

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!

2
As you have multiple pitfalls in your current approach. You can try following : Build Jar -> Package that jar in openjdk docker image - jazz
Have tried this myself, always works as you can use host system to generate jar and dockerfile to wrap it up in image - jazz
Why are you trying to build the jar within the image? Is this a constraint you need to meet? Normally you'd build a jar outside the Docker world and then build an image using this externally-built artifact. Exactly as @jazz says - jannis
To build a java project you obviously will need java on the client or use a multi-stage Dockerfile to do the same. The guide you follow assumes the first. - M. Deinum

2 Answers

2
votes

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.

0
votes

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