What I want to do is build a docker image for my Java application but the following considerations should be true for most compiled languages.
On my build server I want to produce a docker image for my application as the deliverable. For this I have to compile the application using some build tool (typically Gradle, Maven or Ant) and then add the created JAR file to the docker image. As I want the docker image to just execute the JAR file I will of course start from a base image with Java already installed.
In this case my build tool controls the whole process. So it prepares the JAR file and after the JAR is created it calls Docker to create the image. This works as the JAR is created beforehand and Docker can be oblivious of the build process needed to create the JAR.
But my Dockerfile is no longer standalone. It depends on steps to happen outside of Docker for it work. In my Dockerfile I will have a COPY
or ADD
statement that is supposed to copy the JAR file to the image. This statement will fail when the jar is not created beforehand. So just executing the Dockerfile might not work. This becomes a problem if you want to integrate with services that just build using the present Dockerfile like the auto-build feature on DockerHub.
In this case all necessary steps to create the image are added to the Dockerfile so the image can be created by just executing the Docker build.
The main problem with this approach is that there is no way to add to a Dockerfile commands that should be executed outside the docker image being created. This means I have to add my source code and my build tools to the docker image and build my JAR file inside the image. This will result in my image being bigger than it has to be due to all the files added that will be unnecessary at runtime. This will also add extra layers to my image.
As @adrian-mouat pointed out if I would add the sources, build the application and deleted the sources in one RUN statement I could avoid adding unnecessary files and layers to the Docker image. This would mean creating some insane chained command.
In this case we split our build in two: first we create the JAR file using our build tool and upload it to a repository (Maven or Ivy repository). We then trigger a separate Docker build that just adds the JAR file from the repository.
In my opinion the better way would be letting the build tool control the process. This is will result in a clean docker image and as the image is what we want to deliver this is of importance. To avoid having a potentially not working Dockerfile lying around this should be created as part of the build. So no one would accidentally use it to start a broken build.
But this will not allow me to integrate with DockerHub.
Is there another way I am missing?
In the years since I first created this question a lot of stuff has changed. At this point I would advocate using Googel's JIB Tool. It integrates with the most common Java Build Tools (Maven and Gradle) and allows you to create container directly from your build. This is much more concise than any of the old approaches I considered all these years ago.
I found this blog post and video from James Ward that reflects better what is currently state of the art. https://cloud.google.com/blog/topics/developers-practitioners/comparing-containerization-methods-buildpacks-jib-and-dockerfile
You can use Docker to run a Java application in a container with a specific runtime environment.
Docker is a platform for packaging, deploying, and running applications in containers. It can run containers on any system that supports the platform: a developer's laptop, systems on “on-prem,” or in the cloud without modification. Images, the packages Docker uses for applications, are truly cross-platform.
Docker is not necessary for Java. You can run a JVM on an operating system without worrying about Docker. Docker is similar to a JVM in that they are both a level of virtualization, but it is probably not helpful to think of the virtualization provided by Docker as the same as the JVM.
The docker registry hub has a Maven image that can be used to create java containers.
Using this approach the build machine does not need to have either Java or Maven pre-installed, Docker controls the entire build process.
├── Dockerfile ├── pom.xml └── src ├── main │ ├── java │ │ └── org │ │ └── demo │ │ └── App.java │ └── resources │ └── log4j.properties └── test └── java └── org └── demo └── AppTest.java
Image is built as follows:
docker build -t my-maven .
And run as follows:
$ docker run -it --rm my-maven 0 [main] INFO org.demo.App - hello world
FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
If you wanted to optimize your image to exclude the source you could create a Dockerfile that only includes the built jar:
FROM java:8 ADD target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar /opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar CMD ["java","-jar","/opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]
And build the image in two steps:
docker run -it --rm -w /opt/maven \ -v $PWD:/opt/maven \ -v $HOME/.m2:/root/.m2 \ maven:3.3-jdk-8 \ mvn clean install docker build -t my-app .
__
Docker now has a multi-stage build capability. This enables Docker to build an image containing the build tools but only the runtime dependencies.
The following example demonstrates this concept, note how the jar is copied from target directory of the first build phase
FROM maven:3.3-jdk-8-onbuild FROM java:8 COPY --from=0 /usr/src/app/target/demo-1.0-SNAPSHOT.jar /opt/demo.jar CMD ["java","-jar","/opt/demo.jar"]
Structure of java aplication
Demo └── src | ├── main | │ ├── java | │ │ └── org | │ │ └── demo | │ │ └── App.java | │ └── resources | │ └── application.properties | └── test | └── java | └── org | └── demo | └── App.java ├──── Dockerfile ├──── pom.xml
Content of Dockerfile
FROM java:8 EXPOSE 8080 ADD /target/demo.jar demo.jar ENTRYPOINT ["java","-jar","demo.jar"]
Commands to build and run image
- Go to the directory of project.Lets say D:/Demo
$ cd D/demo $ mvn clean install $ docker build demo . $ docker run -p 8080:8080 -t demo
Check that container is running or not
$ docker ps
The output will be
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55c11a464f5a demo1 "java -jar demo.jar" 21 seconds ago Up About a minute 0.0.0.0:8080->8080/tcp cranky_mayer
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With