Search code examples
javamavendocker

How to dockerize a Maven project? How many ways to accomplish it?


I am new to Docker and I don't know how to run a Java project with Maven, even though I have read many documents and tried many methods.

  1. Should I build the image using Dockerfile?
  2. What are the commands to run the Maven project in the host with Dockerfile?

Solution

  • Working example.

    This is not a spring boot tutorial. It's the updated answer to a question on how to run a Maven build within a Docker container.

    Question originally posted 8 years ago.


    Update (2023-10-12)

    This answer is revised to take advantage of the new Builkit engine, which is now the default in Docker. Also includes some optional Kubernetes features.

    I hope this helps

    1. Generate a demo application

    Spring Boot demo application generated using the Spring Initializr

    Using the form select "Maven", "Java" and note that today it defaults to Java 17.

    enter image description here

    2. Create a Dockerfile

    #
    # Build stage
    #
    FROM eclipse-temurin:17-jdk-jammy AS build
    ENV HOME=/usr/app
    RUN mkdir -p $HOME
    WORKDIR $HOME
    ADD . $HOME
    RUN --mount=type=cache,target=/root/.m2 ./mvnw -f $HOME/pom.xml clean package
    
    #
    # Package stage
    #
    FROM eclipse-temurin:17-jre-jammy 
    ARG JAR_FILE=/usr/app/target/*.jar
    COPY --from=build $JAR_FILE /app/runner.jar
    EXPOSE 8080
    ENTRYPOINT java -jar /app/runner.jar
    

    Notes

    1. Decided on the Eclipse Temurin java base image. There is now a rich choice of different Open JDK options today
    2. I prefer debian base images. You can change this choice by looking at available tags
    3. Using the "17-jre-jammy" tag for second stage, to make final image smaller.
    4. The Spring boot initializer just needs a JDK. Maven on-board
    5. Note the the cache mount type. This is a Buildkit innovation to cache dendencies between builds

    3. Login to registry

    docker login myregistry
    

    This is the generic command. Some cloud provider registries have alternative commands like:

    az acr login --name my-azure-registry
    

    4. Build the image

    Buildkit is now the default build engine in Docker. Here I use it to run build on my Kubernetes cluster.

    #
    # (Optional) Run a build pod on your k8s cluster
    #
    docker buildx create --name my-builder --driver kubernetes --driver-opt replicas=1 --use
    
    #
    # Build and push the image (Buildkit "buildx" plugin is now part of Docker)
    #
    docker buildx build --tag  myregistry/myorg/demo:latest --push .
    

    Note:

    1. Normally I'd push more than just the "latest" tag. That is out of scope for this answer.

    Reference:

    5. Run the image

    Run locally using Docker

    docker run -d -p 8080:8080 myregistry/myorg/demo:latest
    

    Deploy to Kubernetes

    kubectl create ns demo
    kubectl -n demo create deployment demo --image=myregistry/myorg/demo:latest --port=8080 --replicas=1
    kubectl -n demo expose deployment demo --type=LoadBalancer
    

    Note:

    1. This is a demo. Not the recommended way to deploy to Kubernetes. I would use a Helm chart (again out of scope of this answer)

    Update (2019-02-22)

    This answer tried to spell out the steps to run Maven within a multi-stage Dockerfile

    1. Generate an application

    Use the spring initializer to generate a demo app

    https://start.spring.io/

    enter image description here

    Extract the zip archive locally

    2. Create a Dockerfile

    #
    # Build stage
    #
    FROM maven:3.6.0-jdk-11-slim AS build
    COPY src /home/app/src
    COPY pom.xml /home/app
    RUN mvn -f /home/app/pom.xml clean package
    
    #
    # Package stage
    #
    FROM openjdk:11-jre-slim
    COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
    EXPOSE 8080
    ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]
    

    Note

    • This example uses a multi-stage build. The first stage is used to build the code. The second stage only contains the built jar and a JRE to run it (note how jar is copied between stages).

    3. Build the image

    docker build -t demo .
    

    4. Run the image

    $ docker run --rm -it demo:latest
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::        (v2.1.3.RELEASE)
    
    2019-02-22 17:18:57.835  INFO 1 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication v0.0.1-SNAPSHOT on f4e67677c9a9 with PID 1 (/usr/local/bin/demo.jar started by root in /)
    2019-02-22 17:18:57.837  INFO 1 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
    2019-02-22 17:18:58.294  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.711 seconds (JVM running for 1.035)
    

    Misc

    Read the Docker hub documentation on how the Maven build can be optimized to use a local repository to cache jars.


    Update (2019-02-07)

    This question is now 4 years old and in that time it's fair to say building application using Docker has undergone significant change.

    Option 1: Multi-stage build

    This new style enables you to create more light-weight images that don't encapsulate your build tools and source code.

    The example here again uses the official maven base image to run first stage of the build using a desired version of Maven. The second part of the file defines how the built jar is assembled into the final output image.

    FROM maven:3.5-jdk-8 AS build  
    COPY src /usr/src/app/src  
    COPY pom.xml /usr/src/app  
    RUN mvn -f /usr/src/app/pom.xml clean package
    
    FROM gcr.io/distroless/java  
    COPY --from=build /usr/src/app/target/helloworld-1.0.0-SNAPSHOT.jar /usr/app/helloworld-1.0.0-SNAPSHOT.jar  
    EXPOSE 8080  
    ENTRYPOINT ["java","-jar","/usr/app/helloworld-1.0.0-SNAPSHOT.jar"]  
    

    Note:

    • I'm using Google's distroless base image, which strives to provide just enough run-time for a java app.

    Option 2: Jib

    I haven't used this approach but seems worthy of investigation as it enables you to build images without having to create nasty things like Dockerfiles :-)

    https://github.com/GoogleContainerTools/jib

    The project has a Maven plugin which integrates the packaging of your code directly into your Maven workflow.


    Original answer (Included for completeness, but written ages ago)

    Try using the new official images, there's one for Maven

    https://registry.hub.docker.com/_/maven/

    The image can be used to run Maven at build time to create a compiled application or, as in the following examples, to run a Maven build within a container.

    Example 1 - Maven running within a container

    The following command runs your Maven build inside a container:

    docker run -it --rm \
           -v "$(pwd)":/opt/maven \
           -w /opt/maven \
           maven:3.2-jdk-7 \
           mvn clean install
    

    Notes:

    • The neat thing about this approach is that all software is installed and running within the container. Only need docker on the host machine.
    • See Dockerfile for this version

    Example 2 - Use Nexus to cache files

    Run the Nexus container

    docker run -d -p 8081:8081 --name nexus sonatype/nexus
    

    Create a "settings.xml" file:

    <settings>
      <mirrors>
        <mirror>
          <id>nexus</id>
          <mirrorOf>*</mirrorOf>
          <url>http://nexus:8081/content/groups/public/</url>
        </mirror>
      </mirrors>
    </settings>
    

    Now run Maven linking to the nexus container, so that dependencies will be cached

    docker run -it --rm \
           -v "$(pwd)":/opt/maven \
           -w /opt/maven \
           --link nexus:nexus \
           maven:3.2-jdk-7 \
           mvn -s settings.xml clean install
    

    Notes:

    • An advantage of running Nexus in the background is that other 3rd party repositories can be managed via the admin URL transparently to the Maven builds running in local containers.