Specify Source directory for Docker Builder Image - docker

Note: I had problems finding a good title. If you have a better idea for the title feel free to edit the title.
In the past I have used the following builder image to containerize the build process of a Spring Boot app.
FROM maven:3.6.3-jdk-11
COPY pom.xml ./
COPY src ./src
RUN mvn clean package
I don't like this container design, because the resulting container image is useless if the source code changes, because if the source code or pom.xml changes I need to rebuild the docker image. Instead I want a builder image which takes a src directory and a pom.xml and executes mvn clean package when running the container.
FROM maven:3.6.3-jdk-11
COPY pom.xml ./
COPY src ./src
ENTRYPOINT ["mvn", "clean", "package"]
I now use mvn clean package as the entrypoint. This way mvn clean package gets execute every time the container starts and not just on docker build. But the problem remains. The src directory and pom.xml is copied during the docker build process.
So here is my question: Is there a way to specify the src directory and pom.xml when executing docker run ? Can I achieve this by mounting a volume?

FROM maven:3.6.3-jdk-11
WORKDIR /usr/src/myapp
ENTRYPOINT ["/usr/local/bin/mvn-entrypoint.sh"]
CMD ["mvn"]
This solution works. Run it with:
docker container run --name build -v "$(pwd)":/usr/src/myapp -w /usr/src/myapp myapp:build mvn clean install
However, for this you can just use the maven image as #timsmelik has suggested in the comments.

Related

running a docker container and trying to check the log of the container prints an error on console

I have built the image into a container, why I try to print the log of the container I run in detach mode I get an error printout :
Error: Could not find or load main class EazyBankApplication
Caused by: java.lang.ClassNotFoundException: EazyBankApplication
Please how can I fix this, I have tried changing the run command to accommodate a directory path and it still persists.
FROM openjdk:17
RUN mkdir /eazyApp
COPY ./src/main/java/com/eazybank/ /eazyApp
WORKDIR /eazyApp
CMD java EazyBankApplication
In your Dockerfile, you're COPYing only a single source directory into the image. When the container tries to execute the CMD java EazyBankApplication, the corresponding .class file doesn't exist.
Many SO Java questions address this by compiling the application on the host, and then COPY the built jar file (but not any of its source) into the image.
FROM openjdk:17
WORKDIR /eazyApp
COPY target/eazybankapplication-0.0.1.jar ./
CMD java -jar eazybankapplication-0.0.1.jar
If you want to build the application inside Docker, then you need to invoke the build tool somehow. A typical approach is to COPY the entire application tree into the image, excluding parts of it via a .dockerignore file.
FROM openjdk:17
WORKDIR /eazyApp
# Copy in the entire source tree
# (.dockerignore file excludes host's build/)
COPY ./ ./
# Build the application
RUN ./gradlew build
# Run it
CMD java -jar build/libs/eazybankapplication-0.0.1.jar
(You could combine the two approaches with a multi-stage build to get a final image without the source code, and possible with only the JRE for a smaller image.)
You probably need a build system like Gradle or Maven in practice, but if you only have the source files, you can still directly use javac.
FROM openjdk:17
WORKDIR /eazyApp
COPY ./src/ ./src/
RUN mkdir class
ENV CLASSPATH=/eazyApp/class
RUN find src -name '*.java' -print > files.txt \
&& javac -dclass #files.txt \
&& rm files.txt
CMD java com.eazybank.EazyBankApplication
Your copy command does not copy the files in the directory, use
COPY ./src/main/java/com/eazybank/* /eazyApp
Also you don't need mkdir command, copy command will create the directory.

RUN a command on build context before copying files in Docker image

I have a git repository as my build context for a docker image, and i want to execute gradle build and copy the jar file into the docker image and run it on entrypoint.
I know that we can copy the entire project into the image and run build and execute, however i want to run build first and copy only the jar executable into the image.
Is it possible to execute commands in the build context before copying the files?
Current way:
FROM azul/zulu-openjdk-alpine:8
COPY ./ .
RUN ./gradlew clean build
ENTRYPOINT ["/bin/sh","-c","java -jar oms-core-app-*-SNAPSHOT.jar"]
What i want:
FROM azul/zulu-openjdk-alpine:8
RUN ./gradlew clean build
COPY ./oms-core-app-*-SNAPSHOT.jar .
ENTRYPOINT ["/bin/sh","-c","java -jar oms-core-app-*-SNAPSHOT.jar"]
I cannot run the ./gradlew clean build before copying the project because the files don't exist in the image when running the command. But i want to run it in the source itself and then copy the jar.
Any help would be highly appreciated thank you.
Conceptually this isn't that far off from what a multi-stage build does. Docker can't create files or run commands on the host, but you can build your application in an intermediate image and then copy the results out of that image.
# First stage: build the jar file.
FROM azul/zulu-openjdk-alpine:8 AS build
WORKDIR /app # avoid the root directory
COPY ./ . # same as "current way"
RUN ./gradlew clean build # same as "current way"
RUN mv oms-core-app-*-SNAPSHOT.jar oms-core-app.jar
# give the jar file a fixed name
# Second stage: actually run the jar file.
FROM azul/zulu-openjdk-alpine:8 # or some JRE-only image
WORKDIR /app
COPY --from=build /app/oms-core-app.jar .
CMD java -jar oms-core-app.jar
So the first stage runs the build from source; you don't need to run ./gradlew build on the host system. The second stage starts over from just the plain Java installation, and COPY only the jar file in. The second stage is what eventually runs, and if you docker push the image it will not contain the source code.

How to solve about Docker error about failed to compute cache key: "/requirements.txt?

I use this docker build - < Dockerfile -t deepface to build a docker image.
When I runin command it show Error:
> ERROR [3/4] COPY ./requirements.txt /requirements.txt
> 0.0s
> ------
> > [3/4] COPY ./requirements.txt /requirements.txt:
> ------ failed to compute cache key: "/requirements.txt" not found: not found
My Director File is
>Deepface
|->Dockerfile
|->requirements.txt
My requirements.txt is
numpy==1.19.5
pandas==1.2.4
gdown==3.13.0
tqdm==4.60.0
Pillow==8.2.0
opencv-python==4.5.2.52
tensorflow==2.5.0
keras==2.4.3
Flask==2.0.1
matplotlib==3.4.2
deepface==0.0.53
and my Dockerfile is
FROM python:3.9
WORKDIR /code
COPY ./requirements.txt /requirements.txt
RUN pip install -r ./requirements.txt
How can I solve this problem?
This could be related to this BuildKit docker issue.
In order to see if this is indeed the problem, try building with BuildKit disabled:
$ DOCKER_BUILDKIT=0 docker build ...
If this helps, then you can try one of these for a permanent fix:
Update docker (or follow the above linked GitHub issue to see if it is fixed)
Disable BuildKit globally by adding
{ "features": { "buildkit": true } }
to /etc/docker/daemon.json
(or c:\Users\CURRENT_USER\.docker\daemon.json on Windows).
As a side note, I would recommend avoiding copying requirements.txt to the root folder of the container. Use a subdirectory, such as /app and use WORKDIR in your Dockerfile to make it the base directory.
As a secondary side note - instead of running
docker build - < Dockerfile ... you can just run
docker build ...
The particular docker build syntax you use
docker build - <Dockerfile
has only the Dockerfile; nothing else is in the Docker build context, and so you can't COPY anything into the image. Even though this syntax is in the docker build documentation I wouldn't use it.
A more typical invocation is to just specify the current directory as the build context:
docker build -t deepface .
(Don't forget to also COPY your application code into the image, and set the standard CMD the container should run.)
1. WHY?
Whenever you piped through STDIN Dockerfile you can't use ADD and COPY instructions within local paths.
There is a trick. Docker can use paths only in scope of so called context.
Context is a directory you specify for docker build. E.g. docker build my-docker-context-dir. But as long as you use STDIN instead of directory there is no directory.
In this case docker is absolutely blind to everything but the contents of Dockerfile. Read this official Build with -
Perhaps its also worth reading whole docker build section. Frankly at first I also skipped it, and got some pitfalls just like you.
2. What to do?
Whenever you want to put some files into docker image, you have to create a context directory.
So your directory structure should be like this:
>Deepface
|->Dockerfile
|->context-dir
|->requirements.txt
Now you can call docker build as follows (note, there is a -t option as proposed by David Maze):
cd Deepface
docker build -t deepface -f Dockerfile context-dir
For some reason the file may be in the .dockerignore file, please check if the file is not there.

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.
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.
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!
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.
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

Docker build image 1GB or too big

I have a Docker build file in my Maven Java project to build it. The docker image ends being about 1GB. All I want to do is build the war artifact and keep the size down. Here is what I have so far:
FROM java:8
#FROM maven:latest
MAINTAINER Sonam <myemail#gmail.com>
RUN apt-get update
RUN apt-get install -y maven
WORKDIR /code
#Prepare by downloading dependencies
ADD pom.xml /code/pom.xml
RUN ["mvn", "dependency:resolve"]
RUN ["mvn", "verify"]
#Adding source, compile and package into a fat jar
ADD src /code/src
RUN ["mvn", "clean"]
RUN ["mvn", "install", "-Dmaven.test.skip=true"]
RUN mkdir webapps
CMD ["echo"] ["hello"]
I have a CoreOS unit file where when this container is run I want to copy the war to another file.
You can see an alternative in "How do I build a Docker image for a Ruby project without build tools?"
I use an image to build,
I commit the resulting stopped container as a new image (with a volume including the resulting binary)
I use an execution image (one which only contain what you need to run), and copy the binary from the other image. I commit again the resulting container.
The final image includes the compiled binary and the execution environment.
In your case, make sure your current Dockerfile declares a volume and produces the war (from the maven compilation) in it.
Then commit the exited container as a new temporary "compilation" image.
Use another Dockerfile which will run and copy the war "from-volume" of your compilation image.
docker run -u root -it --name=installation.cont --volumes-from compilation.cont --entrypoint "/bin/sh" installation.image -c "cp /avolume/myway /to/my/path"
The resulting stopped container installation.cont can be committed as an image installation.image.
Then you can write a third Dockerfile, starting from that image: FROM installation.image (which includes only your war), and adding what you need to in order to use that war.
docker commit apache.inst.cont apache.inst

Resources