Context:
It's an application written in Kotlin and using Spring-boot with Maven.
Basically, I'd like to know if it makes sense what I'm doing.
Running mvn install then the target folder will be created with the corresponding jar file.
Therefore the Dockerfile will be just copying the jar file into the working directory of the container and run java -jar WHATEVER.jar.
Example of the simple Dockerfile
FROM openjdk:8-jre-alpine
COPY target/app-DEV-SNAPSHOT.jar .
EXPOSE 8089
CMD ["java", "-jar", "./app-DEV-SNAPSHOT.jar"]
But I'd say, makes much more sense to me to use the multi-stage building and in the first stage generate the jar file and in the second stage, execute it. I tried this second approach but I'm facing an issue with main class doesn't exist
Multi-stage Dockerfile:
FROM maven:3.5.2-jdk-8-alpine as BUILD
ENV APP_HOME=/usr/src/service
COPY ./src /usr/src/service
COPY pom.xml /usr/src/service
WORKDIR /usr/src/service
RUN mvn install
FROM openjdk:8-jre-alpine
COPY --from=BUILD /usr/src/service/target/app-DEV-SNAPSHOT.jar ./
EXPOSE 8080
CMD ["java", "-jar", "./app-DEV-SNAPSHOT.jar"]
Which one is the correct one?
You should use the multistage dockerfile. Reason being you want to have least dependency on the host system. When you run mvn on host you add dependency of mvn and in turn java.
My recommendation would be to use multistage docker to build in one stage and copy to another stage
Related
I am trying to run my DockerFile and Gradle build inside . I have tried all the ways to do it , but can't understand.
Here is my dockerFile it is working properly, but DO NOT making the gradle BUILD , can someone help me With it:
FROM gradle:4.7.0-jdk8-alpine AS build
COPY . /temp
RUN gradle build --no-daemon
FROM java:8-jdk AS TEMP_BUILD_IMAGE
COPY . /tmp
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
The output is
'<unknown> Dockerfile: DockerFile' has been deployed successfully.
But it is not building me the Gradle. P.S i am new to DOCKER , and maybe i am doing the wrong stuff in my docker FIle
You have a multistage build here that you need to understand.
FROM gradle:4.7.0-jdk8-alpine AS build
COPY . /temp
RUN gradle build --no-daemon
This will create a docker container, copy the complete docker build context into the container and run gradle. You did not show the complete console output so I can only guess that this ran successfully. Also you did not show your build.gradle file so noone can tell you where to search for the compile result.
FROM java:8-jdk AS TEMP_BUILD_IMAGE
COPY . /tmp
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
With these lines you create the next stage's docker container, and again you copy your project into the container. But nowhere I see that you would transport the build output from the first stage into the second stage. As this is missing, the resulting container of course does not contain the build result, and you believe it did not happen.
You need to add a line such as
COPY --from=build /whateverPointsToYourBuildOutput /whereverYouWantItInTheContainer
See https://docs.docker.com/engine/reference/builder/#copy
I reference an HTML file in my code, and access it with:
Path filePath1 = Path.of("./email.html");
When I run the project locally, the project works fine, and the file loads normally. However, when running the project in a Docker container, I get the following error:
java.nio.file.NoSuchFileException: ./email.html
Here is my Docker file for reference
FROM openjdk:11.0-jdk-slim as builder
VOLUME /tmp
COPY . .
RUN apt-get update && apt-get install -y dos2unix
RUN dos2unix gradlew
RUN ./gradlew build
# Phase 2 - Build container with runtime only to use .jar file within
FROM openjdk:11.0-jre-slim
WORKDIR /app
# Copy .jar file (aka, builder)
COPY --from=builder build/libs/*.jar app.jar
ENTRYPOINT ["java", "-Xmx300m", "-Xss512k", "-jar", "app.jar"]
EXPOSE 8080
Thank you for the answers. So this is a Java project, so there is no index.html to add. I tried changing the work directory to /src, but it is still not picking it up
Docker has no access to the filesystem fromm the host OS.
You need to put it in there as well:
COPY ./index.html index.html
There's a couple of options:
Copy the index.html in the docker image (solution by ~dominik-lovetinsky)
Mount the directory with your index.html file as a volume in your docker instance.
Include the index.html as a resource in your app.jar, and access it as a classpath resource.
The last option: including resources as classpath resource, is the normal way webapps work, but I'm not sure if it works for you.
I have this working simple dockerfile.
FROM openjdk:8-jdk-alpine
WORKDIR /data
COPY target/*.jar, myapp.jar
ENTRYPOINT ["java","-jar",myapp.jar]
I build my jar using maven either locally or in a pipeline then use that .jar here. I've seen many examples installing maven in the dockerfile instead of doing the build before. Doesnt that just make the image larger? Is there a benefit of doing that?
Usually I have a CICD server which I use for building my jar file and then I generate a docker image using it. Build a jar consumes resources and doing it when you're running your docker container can take longer depending on your configuration. In a normal CICD strategy, build and deploy are different steps. I also believe your docker image should be as lean as possible.
That's my opinion.
I hope I could help you somehow.
I think you are looking for Multi-stage builds.
Example of multistage Dockerfile:
# syntax=docker/dockerfile:1
FROM golang:1.16
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/alexellis/href-counter/app ./
CMD ["./app"]
Notice the COPY --from=0 ... line, it's copying the result of the build that happens in the first container to the second.
These mutistage builds are good idea for builds that need to install their own tools in specific versions.
Example taken from https://docs.docker.com/develop/develop-images/multistage-build/
I use docker-compose to launch different Spring Boot apps.
My docker images are defined with this kind of Dockerfile:
FROM openjdk:8-jdk-alpine
ADD app.jar app.jar
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app.jar"]
However, I would like to benefit from debugging and hot-reload features using something like mvn spring-boot:run without being dependent of a particular IDE.
What is the best way to accomplish debugging and hot-reloading with Spring Boot in a Docker container without being dependent of a particular IDE?
Notes:
my source files are build into a jar (with Maven) which is copied to a different location containing the definition of my Docker images ; meaning my sources files are not in the docker image.
the reason I want to develop in the Docker container is that my apps depend on each other, and are configured in the docker-compose environment, so I cannot easily run one app alone outside the docker network and environment.
I thought of mounting a volume containing my spring boot projects in the docker containers, and then use mvn spring-boot:run in the container ; but I can't prevent maven to download all dependencies from the internet (I tried specifying a local repository containing all my dependencies without success). I would like to know if this a decent solution and how to make it work.
You have to follow the following steps to build and run spring boot application in docker.
Step-1 : Create a File called Dockerfile in your Project.
Step-2 : Write the Following Code on you Dockerfile
# Use the official maven/Java 8 image to create a build artifact.
# https://hub.docker.com/_/maven
FROM maven:3.6-jdk-11 as builder
# Copy local code to the container image.
WORKDIR /app
COPY pom.xml .
COPY src ./src
# Build a release artifact.
RUN mvn package -DskipTests
# Use AdoptOpenJDK for base image.
# It's important to use OpenJDK 8u191 or above that has container support enabled.
# https://hub.docker.com/r/adoptopenjdk/openjdk8
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM adoptopenjdk/openjdk11:alpine-slim
# Copy the jar to the production image from the builder stage.
COPY --from=builder /app/target/your-app-name*.jar /your-app-name.jar
# Run the web service on container startup.
CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/your-app-name.jar"]
Step-3 : Start Your Docker Desktop Application
Step-4 : Open Your Terminal or Windows PowerShell. Then go to Project Directory.
Step-6 : Write the Following Command to create image for your application (You must have internet connection to download all dependencies).
docker build -f Dockerfile -t your-app-name .
Step-7 : After image creation success. Write the following code to run the image in Docker container.
docker run -p docker-port:app-port image-name
Following your line of thinking you can try to copy your dependencies from a volume into the project container and then use the offline mode in something like this:
FROM openjdk:8-jdk-alpine
WORKDIR /app
# copy the Project Object Model file
COPY ./pom.xml ./pom.xml
# copy your dependencies
COPY app.jar app.jar
# copy your other files
COPY ./src ./src
# Set fetch mode to offline to avoid downloading them from the internet
RUN mvn dependency:go-offline
Apparently it's also possible to configure the offline mode globally by setting the offline property in the ~/.m2/settings.xml file, you can setup that and copy your m2 file and reference it when running the container
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<offline>true</offline>
</settings>
mvn -Dmaven.repo.local=~/.m2/settings.xml ...
You can find more information here:
https://www.baeldung.com/maven-offline
Specifying Maven's local repository location as a CLI parameter
Created basic HelloWorld microservice using Spring Boot (2.1.3), Java 8, Maven.
pom.xml has maven plugin entry like below
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.example.HelloWorldApplication</mainClass>
</configuration>
</plugin>
Dockerfile looks like below
FROM openjdk:8
VOLUME /tmp
ADD target/helloworld.jar helloworld.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","helloworld.jar"]
Created image on local machine using command
docker build . -t helloworld:v1
Verified by creating container out of it.
Checked in code to docker-hub account and github account.
Logged into Google cloud platform (GCP), created kubernetes cluster, created pipeline(using container builder) by configuring github url where helloworld microservice code resides. There are two options to run build (use Dockerfile or cloudbuild.yaml). I am using Dockerfile to run build.
When build is picked up to run, it fails for this line in Dockerfile
ADD target/helloworld.jar helloworld.jar
Error seen in GCP logs:
ADD failed: stat /var/lib/docker/tmp/docker-builderxxxxxx/target/helloworld.jar: no such file or directory
I tried to replace it with COPY command and still the issue is same.
Note: I tried to go with cloudbuild.yaml
Here is how my cloudbuild.yaml looks:
steps:
# Build the helloworld container image.
- name: 'gcr.io/cloud-builders/docker'
args:
- 'build'
- '-t'
- 'gcr.io/${PROJECT_ID}/helloworld:${TAG_NAME}'
- '.'
This didn't make any difference. Issue remains the same.
Any idea if Springboot Java application has some specific configuration for Dockerfile to be built fine in Google Cloud Platform?
UPDATE - 1
Based on comments tried below steps on local machine:
ran command mvn clean . That cleaned target folder
updated Dockerfile
FROM maven:3.5-jdk-8 AS build
COPY src .
COPY pom.xml .
RUN mvn -f pom.xml clean package
FROM openjdk:8
VOLUME /tmp
COPY --from=build target/helloworld.jar helloworld.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","helloworld.jar"]
Ran docker build . -t helloworld:v1 command and that created image.
Then run command to start container:
docker run -p 8081:8081 -n helloworld-app -d helloworld:v1
container starts and exits with error in log:
Exception in thread "main" java.lang.ClassNotFoundException: com.example.HelloWorldApplication at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
Looks like a problem with file paths.
Try the following updated Dockerfile, which explicitly sets the working directory. It also uses explicit file paths when copying the jar between images.
FROM maven:3.5-jdk-8-slim AS build
WORKDIR /home/app
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn clean package
FROM openjdk:8-jre-slim
COPY --from=build /home/app/target/helloworld-0.0.1-SNAPSHOT.jar /usr/local/lib/helloworld.jar
EXPOSE 8081
ENTRYPOINT ["java","-jar","/usr/local/lib/helloworld.jar"]
Additional Notes:
See the related answer for a full example building a spring boot app
I've based the second stage on a JRE image. Reduces the size of the output image.