18

I have the following docker file that runs a spring boot application:

# For Java 11, try this
FROM adoptopenjdk/openjdk11:alpine-jre

#
ARG JAR_FILE=/build/libs/pokerstats-0.0.1-SNAPSHOT.jar

#
WORKDIR /opt/app

#
COPY ${JAR_FILE} app.jar

#
ENTRYPOINT ["java","-jar","app.jar"]

The issue is that currently I have to run gradle clean build on my host machine first to create the jar file on my local machine at path:

/build/libs/pokerstats-0.0.1-SNAPSHOT.jar

How can I put this gradle clean build into my docker file so that the build step is done inside the container?

edit:

I want the steps for a user to be:

  1. Clone my project from github
  2. run docker build -t pokerstats . - which will do the gradle build
  3. run docker container run -d -p 8080:8080 pokerstats

The user will clone my project from github - I then want them to be able to run the docker container without having to build the project with gradle first - I.e. I want the docker file to do the build and copy the jar into the container.

Brian Burns
  • 20,575
  • 8
  • 83
  • 77
java12399900
  • 1,485
  • 7
  • 26
  • 56
  • `The issue is that currently I have to run gradle clean build on my host machine first to create the jar file on my local machine at path` - why is this an issue? – jannis Apr 08 '20 at 19:12
  • Note that you can use a Gradle plugin to build docker images. There are a few out there, for example [com.liferay.app.docker](https://plugins.gradle.org/plugin/com.liferay.app.docker). – jannis Apr 08 '20 at 19:14
  • Because Ideally I would like this step in the docker container as it is one less command for a user to carry out before running my application. Is this possible? – java12399900 Apr 08 '20 at 19:14
  • It's is not clear what you want to do. If the users will only have the docker image there is no point to have them build the project instead of running the jar file. If the users can change the project it mean they will have it installed in their machine. In this case use a ```docker volumes``` – aiqency Apr 08 '20 at 19:32
  • `Is this possible?` - look above - there are plugins that will make this a one-step operation. – jannis Apr 08 '20 at 19:34
  • @aiqency - The user will clone my project from github - I then want them to be able to run the docker container without having to build the project with gradle first - I.e. I want the docker file to do the build and copy the jar into the container. I have edited my question to highlight this. – java12399900 Apr 08 '20 at 19:37
  • But then they have to build the image first, don't they? So what's the difference - they'll have to do SOMETHING this way or another. – jannis Apr 08 '20 at 19:41
  • Indeed, building the project directly in the `docker build` step yields a bit more reproducibility (and does not require having a complete toolchain on the host). To implement this, you could try for instance to rely on a standard maven-based `Dockerfile` such as that of this answer [How to convert a Spring-Boot web service into a Docker image](https://stackoverflow.com/a/54299199), and replace `mvn` commands with gradle ones? – ErikMD Apr 08 '20 at 19:41
  • Docker image is just another artifact produced by your project. Why build it differently than other artifacts (jar package for example)? – jannis Apr 08 '20 at 19:45
  • I agree with jannis If the users are devs and you want them to run the project in a container just let them use volumes – aiqency Apr 08 '20 at 19:53
  • @jannis two possible reasons: (1) to have a comprehensive build system within a container, e.g. for CI purposes ; (2) for reproducibility, see e.g. [this paragraph of a docker.com article](https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/#h.hsy4wrgavnlq) – ErikMD Apr 08 '20 at 19:53
  • @ErikMD thanks,How can I reproduce your maven answer in gradle? this is what I am looking for I think – java12399900 Apr 08 '20 at 22:20
  • 1
    https://codefresh.io/docs/docs/learn-by-example/java/gradle/ – jannis Apr 09 '20 at 21:38
  • 1
    https://medium.com/@sairamkrish/docker-for-spring-boot-gradle-java-micro-service-done-the-right-way-2f46231dbc06 – jannis Apr 09 '20 at 21:41
  • "build gradle within docker multistage spring-boot" is the phrase I used to find these links – jannis Apr 09 '20 at 21:42
  • @jannis thanks - feel free to answer the question or else I can add my answer as I have found a solution using that medium article you linked – java12399900 Apr 09 '20 at 22:32
  • `or else I can add my answer` please do. – jannis Apr 09 '20 at 22:33
  • I have added my answer now - feel free to add any feedback on my docker file as I not sure it is 100% optimal. – java12399900 Apr 09 '20 at 22:36

3 Answers3

28

After reading this article I have been able to solve this using a Multi Stage Docker Build. Please see the Docker file below:

# using multistage docker build
# ref: https://docs.docker.com/develop/develop-images/multistage-build/
    
# temp container to build using gradle
FROM gradle:5.3.0-jdk-alpine AS TEMP_BUILD_IMAGE
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY build.gradle settings.gradle $APP_HOME
  
COPY gradle $APP_HOME/gradle
COPY --chown=gradle:gradle . /home/gradle/src
USER root
RUN chown -R gradle /home/gradle/src
    
RUN gradle build || return 0
COPY . .
RUN gradle clean build
    
# actual container
FROM adoptopenjdk/openjdk11:alpine-jre
ENV ARTIFACT_NAME=pokerstats-0.0.1-SNAPSHOT.jar
ENV APP_HOME=/usr/app/
    
WORKDIR $APP_HOME
COPY --from=TEMP_BUILD_IMAGE $APP_HOME/build/libs/$ARTIFACT_NAME .
    
EXPOSE 8080
ENTRYPOINT exec java -jar ${ARTIFACT_NAME}
joshmcode
  • 3,471
  • 1
  • 35
  • 50
java12399900
  • 1,485
  • 7
  • 26
  • 56
  • 1
    What's the use of running gradle build twice ? – Nicolas Feb 16 '21 at 14:15
  • 5
    I suspect there isn't one - there are some other lines in this Dockerfile that don't look quite right either: "COPY . .", copying the project source with the chown option and then chowning it again, copying gradle from the original location in the image - these things make me suspect that running gradle twice is an oversight. – ultrafez Mar 05 '21 at 09:48
  • 2
    I agree some of this isn't needed, but I _think_ the `COPY . .` following the build is caching the dependencies. Copy the gradle files and build -- which will fail because the src is missing. Then copy over everything else and build again, but now the deps are in a previous step so changes to src won't mean redownloading/buidling dependencies. – Ian Pringle Nov 30 '21 at 17:21
  • you restart docker containers each time you do a build. do you lose gradle daemon optimisations compared to a gradle running on a host machine? do you care? – mishka Jul 07 '22 at 14:42
5

Mutli stage build. Be sure to have build.gradle and settings.gradle files included in your project directory, I tried to make it as clean and concise as possible:

#Build stage

FROM gradle:latest AS BUILD
WORKDIR /usr/app/
COPY . . 
RUN gradle build

# Package stage

FROM openjdk:latest
ENV JAR_NAME=app.jar
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY --from=BUILD $APP_HOME .
EXPOSE 8080
ENTRYPOINT exec java -jar $APP_HOME/build/libs/$JAR_NAME  

Make sure to change JAR_NAME variable to your generated jar file name.

Abdelsalam Megahed
  • 1,281
  • 12
  • 13
1

Build stage

FROM gradle:jdk11-alpine AS BUILD_STAGE
COPY --chown=gradle:gradle . /home/gradle
RUN gradle build || return 1

Package stage

FROM openjdk:11.0.11-jre
ENV ARTIFACT_NAME=app.jar
ENV APP_HOME=/app
COPY --from=BUILD_STAGE /home/gradle/build/libs/$ARTIFACT_NAME $APP_HOME/
WORKDIR $APP_HOME
RUN groupadd -r -g 1000 user && useradd -r -g user -u 1000 user
RUN chown -R user:user /app
USER user
ENTRYPOINT exec java -jar ${ARTIFACT_NAME}
K.Mat
  • 1,341
  • 11
  • 17
Ariel
  • 11
  • 2
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Nov 29 '22 at 13:50