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
A GitLab codebase is created.
A Maven repository is created.
A Container Registry Enterprise Edition instance is created. For more information, see Create a Container Registry Enterprise Edition instance.
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.
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.
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:
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.
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.