Use a Dockerfile to build source code into a container image, and then distribute and deploy that image. Java projects are harder to containerize than Golang or Python projects, and they often build more slowly. Enterprises usually rely on self-managed dependency repositories such as Maven, and developers are often unfamiliar with the Dockerfile caching mechanism. This topic starts from a typical scenario: a self-managed GitLab code repository and a self-managed Maven repository on the cloud. It shows you how to build a Java project with a Dockerfile and how to speed up each build. It also shows how to automate image builds with the build service of Container Registry Enterprise Edition (ACR EE).
Prerequisites
-
You have created a GitLab code repository.
-
You have created a 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 your project references to the self-managed Maven repository before you build any image. 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 Dockerfile is an improved version. The first build takes 43 seconds. If you change only the source code, subsequent builds take 7 seconds.
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
ACR EE provides enterprise-level build services and is recommended for enterprise customers. For more information, see Build images using an Enterprise Edition instanceBuild 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 modeBuild container images in secure VPC mode.
-
Use the immutable image version feature
Enable the immutable image version feature for the repository to prevent online versions from being overwritten. On the Create Image Repository page, in the Repository Information step, set Image Version to Immutable by selecting the check box. After you select the check box, no image version in the repository except
latestcan be overwritten. This keeps container image versions consistent. -
Create a build rule based on commit IDs
Each build generates two image versions. One version uses the code commit ID, so you can map an image version to a code version. The other version uses
latestto mark the most recent version. In the Modify Building Rule dialog box, in the Image Version step, the Commit ID and latest image versions are configured.Commit the code to trigger a build. The following example shows two builds that two code commits triggered automatically. Each build generates two images, and the second build is faster because it hits the cache. On the build settings page, the Automatically Build Images upon Code Change switch is enabled. In the build 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 seconds and 106 seconds respectively.