You can use a Dockerfile to build source code into a container image for distribution and deployment. Compared with Golang or Python projects, Java projects are more difficult to containerize and often have slower build speeds. These challenges arise because enterprises typically use self-hosted dependency repositories, such as Maven, and developers may be unfamiliar with Dockerfile caching mechanisms. This topic uses a typical user scenario that involves a self-hosted GitLab code repository and a self-hosted Maven repository on the cloud to describe how to build a Java project by using a Dockerfile, accelerate the build process, and use the build service of Alibaba Cloud Container Registry Enterprise Edition (ACR EE) for automated image building.
Prerequisites
-
You have created a GitLab code repository.
-
You have created a Maven repository. This topic uses an Alibaba Cloud DevOps Maven repository as an example. For more information, see Alibaba Cloud DevOps Maven repository.
-
You have created a Container Registry Enterprise Edition instance. For more information, see Create an Enterprise Edition instance.
Project overview
This topic demonstrates a build process using two interdependent Java projects. The projects are as follows:
-
Provider: Provides a callable service.
-
Core module: Provides public interfaces.
-
Service module: The service implementation module.
-
-
Consumer: Calls the Provider service.
-
Service module: Depends on the Core module in the Provider project. The following code provides an example:
. ├── 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
-
Build artifacts:
-
A producer application image built from the Provider project.
-
A consumer application image built from the Consumer project.
Step 1: Ensure public dependencies are uploaded
Upload the public dependency packages that the project references to your self-managed Maven repository. For the Provider project, run the following upload command in the Provider directory:
mvn clean install org.apache.maven.plugins:maven-deploy-plugin:2.8:deploy -DskipTests
Step 2: Create a custom Maven base image
To access your self-managed Maven repository in the containerized build environment, add the Maven repository configuration to the base image. Create a custom public Maven base image for your enterprise based on the official Maven image. Application projects can then reference this base image to access the Maven repository.
-
Save the following content as a Dockerfile and place it in the same directory as the settings.xml file of the Maven repository.
FROM maven:3.8-openjdk-8 # Use a Maven image that matches the project. This example uses Maven 3.8. ADD settings.xml /root/.m2/ # Add the self-managed Maven repository configuration to the specified path. -
Run the following commands to build the image and push it 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 for the Provider project)
Run the following command. (Push all base images required during the build process to an 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: Optimize the build speed
In Step 3: Build the Consumer application image (skip for the Provider project), you built an image. However, if you modify the code and rebuild, you will notice that the build process is slow because it downloads the JAR packages again instead of using the cache. Docker invalidates the cache for a layer when its content changes. After you modify the source code, the hash of the files in the ADD instruction changes. This change forces the RUN instruction to execute again, ignoring the cache from the previous build. For more information, see Dockerfile best practices.
To optimize the build speed, cache and reuse the Maven dependencies:
-
First, copy the project's pom.xml file into the container to download the dependencies. As long as the pom.xml file does not change, subsequent builds can use the cache.
-
Copy the project's source code and compile it.
The following is an improved Dockerfile. The first build takes 43 s. If you only change the source code, subsequent builds take only 7 s.
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: Automated build process with Container Registry Enterprise Edition
Container Registry Enterprise Edition (ACR-EE) provides enterprise-level build services and is recommended for enterprise customers. For more information, see Build images using an Enterprise Edition instance.
The following sections outline several best practices.
-
Use the VPC build mode
For self-managed GitLab instances on the cloud, use the secure VPC build mode to build images. This prevents exposing services to the public internet. For more information, see Build container images in secure VPC mode.
-
Use the immutable image tag feature
Enable the immutable tag feature for the repository to prevent production versions from being overwritten. On the Create Image Repository page, in the Repository Information step, set Image Version to Immutable by selecting the checkbox. After you select this option, no image tag in the repository, except for
latest, can be overwritten. This ensures container image version consistency. -
Create a building rule based on commit IDs
Each build generates two image tags. One tag is the code commit ID, which helps map image versions to code versions. The other tag is
latest, which indicates the most recent version. In the Modify Building Rule dialog box, in the Image Version step, the Commit ID and latest image tags are configured.Commit the code to trigger a build. The following example shows two builds automatically triggered by two code commits. Each build generates two images, and the second build is faster due to cache hits. On the build settings page, the Automatically Build Images upon Code Change switch is enabled. In the building rule settings, Branch is set to
branches:master, the build context directory is/, and the Dockerfile name isDockerfile. The image version includes two tags, Configuration 1 and Configuration 2, which correspond to the commit ID version and the latest version. The build log shows that both builds completed successfully, taking 85 s and 106 s, respectively.