55

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.

problem

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.

There are three ways of doing this:

let the build tool control the process

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.

let Docker control the build

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.

Edit:

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.

two separate builds

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.

conclusion

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.

question

Is there another way I am missing?

update June 2020

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.

update February 2021

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

Tobias Kremer
  • 1,531
  • 2
  • 15
  • 21
  • why would you need to integrate with DockerHub when you already have build tools in place that can be automated? – Thomasleveil Jul 29 '15 at 11:46
  • @Thomasleveil because I do not want to run a separate CI server that builds the docker image and pushes it to the registry at DockerHub when I could get it for free from DockerHub. Would just be great to be able to use the auto-build feature on DockerHub with Java applications but I do not see how to best do this. – Tobias Kremer Jul 29 '15 at 12:11
  • If you really want to profit from the free DockerHub automation service, then it looks like you will have to make compromises and have docker do the Ant/Maven/Gradle build – Thomasleveil Jul 29 '15 at 12:16
  • 1
    about _"This would mean creating some insane chained command"_, not necessarily, you could put those commands in a shell script, `COPY` the shell script into your image, then use one `RUN` instruction to call the script → produces one layer – Thomasleveil Jul 29 '15 at 12:17
  • Can you please clarify if you want to use your own build server ( "On my build server I want to produce a docker image" ) or if a condition is to use the "Docker autobuild feature" as mentioned in the comments above ( and thus excluding own infrastrcuture)? – Hendrik Jander Jan 24 '16 at 21:36
  • Question should be "How to build a docker image for a java app" – Jacques Koorts Jun 24 '19 at 15:45

6 Answers6

37

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.

Example

├── 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

Dockerfile

FROM maven:3.3-jdk-8-onbuild
CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"]

Update

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 .

__

Update (2017-07-27)

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"]
Mark O'Connor
  • 76,015
  • 10
  • 139
  • 185
  • How is this different from installing the build tools (in this case Maven) myself? You would still end up with a docker image that does contain not required files like the sources and Maven. – Tobias Kremer Jul 30 '15 at 08:34
  • @TobiasKremer Updated example to illustrate how to build a container excluding the source. – Mark O'Connor Jul 31 '15 at 00:49
  • Where is this my-maven coming from ? How is the Maven build tool present if we use the updated Dockerfile that is based on java:8 only ? – Stephane Jul 04 '16 at 20:44
  • 1
    @Stephane The first example uses a base image that contains both Java and Maven. The source code is built inside the container. The second example is in response to Tobias comment that he preferred the source not to be inside the container, requiring a two step process, firstly to build the jar secondly to create a container containing a Java base image and the built jar. – Mark O'Connor Jul 06 '16 at 00:58
  • I have a question to Mark O'Connor regarding this: In bold part Dockerfile, where you used onbuild image, you've put path to .jar like: "/usr/src/app/". I wonder how to you know is correct path? I'm trying to make mvn package inside docker but I can't figure out where docker actually stores .jar inside of it. How to find out that? – DarioBB Jan 05 '17 at 17:28
  • 1
    @DarioBB The Docker file used to build the "maven:3.3-jdk-8-onbuild" image sets the working directory to /usr/src/app and copies the local files into this location. See: https://github.com/carlossg/docker-maven/blob/33eeccbb0ce15440f5ccebcd87040c6be2bf9e91/jdk-8/onbuild/Dockerfile#L4-L6. How did I discover this Docker file? Linked from DockerHub: https://hub.docker.com/_/maven/ – Mark O'Connor Jan 05 '17 at 18:52
  • Mark, and without onbuild it doesen't have working directory.. how is that so? https://github.com/carlossg/docker-maven/blob/33eeccbb0ce15440f5ccebcd87040c6be2bf9e91/jdk-8/Dockerfile Does it mean that if I'm using it files will nowhere be saved (for example my jar which I am trying to do with mvn package inside docker? – DarioBB Jan 05 '17 at 18:58
  • What if you try to generate your .jar inside docker, what would be path for it? For example using: RUN ["mvn", "verify"] See please if you can answer to this question: http://stackoverflow.com/questions/41491698/problems-with-running-jar-file-after-mvn-package-inside-dockerfile – DarioBB Jan 05 '17 at 19:06
  • @DarioBB You've quoted the Docker file for the "maven:3-jdk-8" image, used to build the base image of "maven:3.3-jdk-8-onbuild". One creates an image with Maven installed, the second layers on top the ONBUILD instructions. The result is cumulative. Docker images are multi-layered – Mark O'Connor Jan 05 '17 at 19:06
  • @DarioBB Yes, you could use the "maven:3-jdk-8" as your base, place the source code wherever you want, set your own working directory and run your own "mvn" command. Personally I prefer the convenience of using the onbuild image. Nice to have options :-) – Mark O'Connor Jan 05 '17 at 19:08
  • But where would be my .jar stored in docker? I just cannot grab proper .jar or something is wrong in maven compilation on docker side in my example – DarioBB Jan 05 '17 at 19:12
  • 1
    @DarioBB Review the Dockerfile for the onbuild image again. It sets the working directory to "/usr/src/app". Secondly it provides two ONBUILD instructions. The first will copy your source code to "/usr/src/app" and the second will run "mvn install". What does that mean? Your jar file will be created inside the "/usr/src/app/target" directory – Mark O'Connor Jan 05 '17 at 19:18
  • 1
    Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/132432/discussion-between-mark-oconnor-and-dariobb). – Mark O'Connor Jan 05 '17 at 19:22
  • Looks like that maven repo is no longer providing "onbuild" images for new versions of maven. – Mike D Apr 17 '18 at 03:05
  • Is this answer up to date? – Koray Tugay Apr 07 '19 at 16:02
  • This answer has been updated recently. https://stackoverflow.com/questions/27767264/how-to-dockerize-maven-project-and-how-many-ways-to-accomplish-it/27768965#27768965 – Mark O'Connor Apr 10 '19 at 10:24
  • Docker images. Not Docker containers. Your link points to building images, but you write containers. – Jacques Koorts Jun 24 '19 at 15:47
  • 1
    Note that Maven is not designed for sharing `~/.m2/repo`. You should only do this in situations where it is certain only one instance run at a time. – Thorbjørn Ravn Andersen Jan 16 '20 at 21:44
  • IMO, the version should not be hard-coded in your Dockerfile. You can use maven-resources-plugin to fix that, but there are better Maven plugins for actually making Docker images without writing your own – OneCricketeer Apr 15 '23 at 04:29
  • @OneCricketeer No argument. In our production we do things differently, having a background process that updates all dependencies and submits this as PR. Files with dependencies are: Dockerfiles, pom.xml and Chart.yaml (helm charts). To conclude please understand this answer is now nearly 8 years old :-) – Mark O'Connor Apr 17 '23 at 13:47
  • Sure, but that wouldn't necessarily require updating the files copied into the Dockerfile. The base container could be updated, but the pom version of the actual project doesn't have to be. I understand the answer is old... Doesn't change this was always bad practice – OneCricketeer Apr 17 '23 at 13:55
  • Let's agree to disagree. I like to have my versions explicitly stated. As I said, we have an automated process that updates dependencies in various files like the Dockerfile. Lastly I don't work exclusively with Java, so a Maven resources plugin solution doesn't work in all cases. Good day. – Mark O'Connor Apr 19 '23 at 10:49
6

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
Riddhi Gohil
  • 1,758
  • 17
  • 17
  • This will not include external dependencies in the JAR, so you get ClassNotFound outside of this simple example. Besides, you don't even need a JAR here. You can run `javac` (or `mvn compile`) and copy the class files, and just use `java App` as the entrypoint – OneCricketeer Apr 15 '23 at 04:30
4

The easiest way is to let the build tool control the process. Otherwise, you would have to maintain your build tool's build file (like pom.xml for Maven or build.gradle for Gradle) as well as a Dockerfile.

A simple way to build a Docker container for your Java app is to use Jib, which is available as Maven and Gradle plugins.

For example, if you are using Maven and want to build your container to your running Docker daemon, you can just run this one command:

mvn compile com.google.cloud.tools:jib-maven-plugin:0.9.2:dockerBuild

You can also build directly to a Docker registry with Jib without needing to install docker, run a Docker daemon (which requires root privileges), or write a Dockerfile. It's also faster and builds images reproducibly.

See more about Jib at its Github repo: https://github.com/GoogleContainerTools/jib

1

A couple of things:

  • If you delete files in the same instruction you add them, they won't consume space in the image. If you look at some of the Dockerfiles for the official images you will see they download source, build it and delete it all in the same step (e.g. https://github.com/docker-library/python/blob/0fa3202789648132971160f686f5a37595108f44/3.5/slim/Dockerfile). This does mean you need to do some annoying gymnastics, but it is perfectly doable.

  • I don't see the problem with two separate Dockerfiles. The nice thing about this is that you could use the JRE rather than the JDK to host your jar.

Elnur Abdurrakhimov
  • 44,533
  • 10
  • 148
  • 133
Adrian Mouat
  • 44,585
  • 16
  • 110
  • 102
1

Containerize your java application using Jib tool without writing dockerfile

Jib is an open-source Java tool maintained by Google for building Docker images of Java applications. It simplifies containerization since with it, we don’t need to write a dockerfile. And actually, we don’t even have to have docker installed to create and publish the docker images ourselves.

Google publishes Jib as both a Maven and a Gradle plugin. https://github.com/GoogleContainerTools/jib

Containerize your java application using Maven project

https://github.com/GoogleContainerTools/jib/tree/master/jib-maven-plugin#quickstart

Containerize your java application using Gradle project

https://github.com/GoogleContainerTools/jib/tree/master/jib-gradle-plugin#quickstart

anandchaugule
  • 901
  • 12
  • 20
  • 1
    I prefer jib. As baeldung said: "There are a couple of other tools out there, too, like Spotify’s docker-maven-plugin and dockerfile-maven plugins, though the former is now deprecated and the latter requires a dockerfile." – Jacques Koorts Jun 24 '19 at 15:50
  • This just seems to repeat the other answer about Jib, posted a year before – OneCricketeer Apr 15 '23 at 04:35
0

there are alternative usages for running jar or war packages

  • add jar into image.
  • set heapsize for java
  • run jar command via entrypoint

sample dockerfile

FROM base
ADD sample.jar renamed.jar
ENV HEAP_SIZE 256m
ENTRYPOINT exec java -Xms$HEAP_SIZE -Xmx$HEAP_SIZE -jar renamed.jar

in addition package deployment example on tomcat

FROM tomcat7
ADD sample.war ${CATALINA_HOME}/webapps/ROOT.war
CMD ${CATALINA_HOME}/bin/catalina.sh run

Building dockerfiles as an image

cp tomcat.dockerfile /workingdir/Dockerfile
docker build -t name /workingdir/Dockerfile .

List images

docker images

Use image to create a container

docker run --name cont_name --extra-vars var1=val1 var2=val2 imagename
pmoksuz
  • 21
  • 5