All Products
Search
Document Center

Container Registry:Best practices for building a container image based on a Java project

Last Updated:Sep 07, 2024

You can use a Dockerfile to build source code into a container image and then distribute and deploy the image. Enterprises usually build container images based on Java projects in self-managed repositories such as a Maven repository. This makes the image building tasks of Java projects more difficult than image building tasks of Golang and Python projects. In addition, enterprises may not be familiar with the caching mechanism of Dockerfiles. This slows down the image building process of container images. This topic describes how to use a Dockerfile to build a container image based on a Java project and how to accelerate the building process. This topic also describes how to use the Container Registry Enterprise Edition to automate image building. The scenarios in this topic include self-managed GitLab codebases and self-managed Maven repositories in the cloud.

Prerequisites

Projects

In this topic, the following Java projects that have dependencies are used:

  • Provider: is invoked by the Consumer project to provide services.

    • Core module: provides common interfaces.

    • Service module: implements services.

  • Consumer: invokes the Provider service.

    • Service module: depends on the Core module in the Provider project. Sample code:

      .
      ├── consumer
      │ ├── Dockerfile
      │ ├── consumer.iml
      │ ├── pom.xml
      │ └── service
      │     ├── pom.xml
      │     ├── src
      │     └── target
      └── provider
          ├── Dockerfile
          ├── core
          │ ├── pom.xml
          │ ├── src
          │ └── target
          ├── pom.xml
          ├── provider.iml
          └── service
              ├── pom.xml
              ├── src
              └── target

Output:

  • A producer application image is built based on the Provider project.

  • A consumer application image is built based on the Consumer project.

Step 1: Upload common dependencies

You must upload the common dependencies that are referenced by the Java projects to the self-managed Maven repository before you build the Java projects. In this example, run the following upload command in the directory of the Provider project:

mvn clean install org.apache.maven.plugins:maven-deploy-plugin:2.8:deploy -DskipTests

Step 2: Create a proprietary Maven base image

To access the self-managed Maven repository in a containerized building environment, you must put the configuration of the Maven repository into the Maven base image. We recommend that you create an enterprise-proprietary public Maven base image based on an official Maven image. You can reference the Maven base image in the application projects to access the Maven repository.

  1. Save the following file as a Dockerfile and store the Dockerfile in the same directory as the settings.xml file in the Maven repository.

    FROM maven:3.8-openjdk-8 # Specifies a Maven image for the projects. In this example, a Maven image of version 3.8 is specified. 
    
    ADD settings.xml /root/.m2/ # Puts the configuration of the self-managed Maven repository into the corresponding position.

  2. Run the following command to build the image and push the image to the remote image repository.

    ls
    Dockerfile   settings.xml
    
    docker build -t demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/maven-base:3.8-openjdk-8 -f Dockerfile .
    Sending build context to Docker daemon   7.68kB
    Step 1/2 : FROM maven:3.8-openjdk-8
     ---> a3f42bfde036
    Step 2/2 : ADD settings.xml /root/.m2/
     ---> db0d5a5192e3
    Successfully built db0d5a5192e3
    Successfully tagged demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/maven-base:3.8-openjdk-8
    
    docker push demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/maven-base:3.8-openjdk-8

Step 3: Build the Consumer application image (Skip this step if you use the Provider project)

Run the following command. We recommend that you push all the base images that are required to build the Consumer project to the Alibaba Cloud image repository.

FROM demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/maven-base:3.8-openjdk-8 AS builder

# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./service service/

# package jar
RUN mvn clean package

# Second stage: minimal runtime environment
From demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/openjdk:8-jre-alpine

# copy jar from the first stage
COPY --from=builder service/target/service-1.0-SNAPSHOT.jar service-1.0-SNAPSHOT.jar

EXPOSE 8080

CMD ["java", "-jar", "service-1.0-SNAPSHOT.jar"]

Step 4: Accelerate the building process of container images based on Java projects

In Step 3: Build the Consumer application image (Skip this step if you use the Provider project), you can build images. However, the JAR package is pulled each time you modify the code and repeat image building. When JAR package caches are not used, images are built at low speed. A Dockerfile has its own caching mechanism. After you modify the source code, the file content in the ADD command is processed by using hash algorithms and values in the ADD command change. As a result, the RUN command must be rebuilt, and the previous building result caches cannot be used. For more information, see Best practices for writing Dockerfiles.

To accelerate image building, you can cache and reuse Maven dependencies. Perform the following operations:

  1. Copy the pom.xml file of the project into the container and download the dependencies. This way, the caches can be reused in subsequent image building if only the pom.xml file is not modified.

  2. Copy the source code of the project and compile the source code.

The following sample code provides an example of an improved Dockerfile. The first time you build a Java project, the time required for the building process is 43 seconds. For subsequent projects, the time required for the building process is 7 seconds if you only modify the source code.

FROM demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/maven-base:3.8-openjdk-8 AS builder

# To resolve dependencies in a safe way (no re-download when the source code changes)
ADD ./pom.xml pom.xml
ADD ./service/pom.xml service/pom.xml
RUN  mvn install

ADD ./service service/

# package jar
RUN mvn clean package

# Second stage: minimal runtime environment
From demo-registry-vpc.cn-beijing.cr.aliyuncs.com/demo/openjdk:8-jre-alpine

# copy jar from the first stage
COPY --from=builder service/target/service-1.0-SNAPSHOT.jar service-1.0-SNAPSHOT.jar

EXPOSE 8080

CMD ["java", "-jar", "service-1.0-SNAPSHOT.jar"]

Step 5: Automate image building by using Container Registry Enterprise Edition

Container Registry Enterprise Edition provides enterprise-level service building capabilities. We recommend that you use Container Registry Enterprise Edition to automate image building. For more information, see Use a Container Registry Enterprise Edition instance to build an image.

The following part describes a few best practices that you can use when you use Container Registry Enterprise Edition instances to build images.

  • Use the VPC mode

    We recommend that you package source code in a self-managed GitLab codebase that is deployed on the cloud to a container image in a virtual private cloud (VPC). This way, you do not need to expose your services to the Internet. For more information, see Build a container image in a VPC.

  • Configure the image tags in the repository to be immutable

    We recommend that you configure the image tags in the repository to be immutable to prevent online tags from being overwritten.创建镜像仓库

  • Create a building rule based on commit IDs

    Two image tags are generated each time you build an image. One tag is represented by using the code commit ID. The image tag is mapped to the code version. The other tag is represented by using latest to indicate the latest tag. 修改构建规则

    Commit the code to trigger the building. Commit the code to trigger the building. The following figure shows the two building processes that are automatically triggered after the code is committed twice. Two image tags are generated in each building process. The second building process is faster than the first building process due to cache hits. 1