How to produce minimal Docker image which runs Java application built using Maven?
Building Docker image
Docker provides feature of multi-stage builds which allows dramatically reduce the size of resulting image and keep Dockerfile simple and maintainable.
This feature will be used to build example Java application.
Java application
The example application uses Functional Java as example of external library to slightly complicate building process:
package com.scalabledeveloper.multistagebuild;
import static fj.data.List.list;
import fj.data.List;
import static fj.Show.intShow;
import static fj.Show.listShow;
public class App {
public static void main(String[] args) {
System.out.println("Functional Java!");
final List<Integer> a = list(1, 2, 3, 4, 5).map(i -> i * 2);
listShow(intShow).println(a);
// List(2,4,6,8,10)
}
}
Maven
Goal is to build executable .jar
file which could used to run application:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.scalabledeveloper</groupId>
<artifactId>multistagebuild</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava</artifactId>
<version>4.8</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-java8</artifactId>
<version>4.8</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-quickcheck</artifactId>
<version>4.8</version>
</dependency>
<dependency>
<groupId>org.functionaljava</groupId>
<artifactId>functionaljava-java-core</artifactId>
<version>4.8</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Apache Maven Assembly Plugin helps to build
executable .jar
file.
Docker
Dockerfile uses power of multi-stage builds:
FROM maven:3.5-jdk-8-alpine as builder
COPY src /usr/src/app/src
COPY pom.xml /usr/src/app
RUN mvn -f /usr/src/app/pom.xml clean package
FROM java:8
COPY --from=builder /usr/src/app/target/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar /usr/app/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar
ENTRYPOINT ["java", "-cp", "/usr/app/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar", "com.scalabledeveloper.multistagebuild.App"]
Main feature is to build things and copy results of builds to target image. There could be a lot of stages. And Docker will remove intermediate results.
Building and running
Building image:
$ docker build -t multistagebuild .
Sending build context to Docker daemon 43.52kB
Step 1/7 : FROM maven:3.5-jdk-8-alpine as builder
---> 074bb2ef6669
Step 2/7 : COPY src /usr/src/app/src
---> Using cache
---> cec7781c9798
Step 3/7 : COPY pom.xml /usr/src/app
---> Using cache
---> 628a9ab1d6b0
Step 4/7 : RUN mvn -f /usr/src/app/pom.xml clean package
---> Using cache
---> f598cd1a66a7
Step 5/7 : FROM java:8
---> d23bdf5b1b1b
Step 6/7 : COPY --from=builder /usr/src/app/target/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar /usr/app/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar
---> 06d0e7db71db
Step 7/7 : ENTRYPOINT ["java", "-cp", "/usr/app/multistagebuild-1.0-SNAPSHOT-jar-with-dependencies.jar", "com.scalabledeveloper.multistagebuild.App"]
---> Running in 766a2508625c
Removing intermediate container 766a2508625c
---> 5783705270fc
Successfully built 5783705270fc
Successfully tagged multistagebuild:latest
Running image:
$ docker run -it multistagebuild
Functional Java!
List(2,4,6,8,10)
Summary
Using Docker multi-stage build feature size of resulting image is reduced and overall process of building image (described in Dockerfile) is easy to support and extend.