The multi-stage builds feature of Docker allows you to specify multiple FROM statements in your Dockerfile. Each FROM statement can use a different base image, and each FROM statement begins a new build stage. Multi-stage builds for Java applications bring benefits such as high security, fast building, and small image size. This topic describes how to build an image for a Java application by using Container Registry and multi-stage builds. In this example, an image is built for a Java application that is developed with Maven and uses GitHub to manage its source code repository.
Prerequisites
- The Container Registry service is activated.
- A Java application is created and its source code is hosted in a repository on GitHub, GitLab, Bitbucket, or Alibaba Cloud Codeup.
Note You can use this Java application that is developed with Maven and uses GitHub to manage its source code repository to experience multi-stage builds.
Background information
Common issues in Docker image building
- It is difficult to write a Dockerfile.
When you get used to using the powerful frameworks of programming languages, especially Java, to conveniently build applications, you may find it difficult to write Dockerfiles to build application images.
- The final image may be large in size.
When you build an image, you may include the application compilation, test, and packaging processes in the same Dockerfile. Each command in the Dockerfile creates a layer of the image, which complicates the structure of the image and enlarges the image size.
- The source code may be leaked.
You may package the source code of your application in the final image, which may lead to code leakage.
Benefits of multi-stage builds
- The final image is built in a secure way.
In the first stage of image building, you must specify an appropriate base image. Then, you need to copy source code to the base image, download application dependencies, compile the source code, test the application, and package the application. In the second stage, you must specify another appropriate base image and copy runtime dependency files generated in the first stage to the base image. This way, the final image does not contain the source code.
- The final image has fewer layers and a smaller size.
The final image contains only a base image and compiled artifacts, which occupy few layers and require a small storage size.
- The final image is built at a fast speed.
You can use build tools such as Docker or Buildkit to concurrently run multiple build processes, which improves the build speed.
Step 1: Create a Dockerfile with multi-stage builds
- First stage: Specify the Maven base image, copy source code to the base image, and then run the RUN command to create a JAR package. If the Java application is developed with Gradle, you can specify a Gradle base image in this stage.
- Second stage: Copy the JAR package generated in the first stage to the OpenJDK image and run the CMD command to generate the final image.
# First stage: complete build environment
FROM maven:3.5.0-jdk-8-alpine AS builder
# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/
# package jar
RUN mvn clean package
# Second stage: minimal runtime environment
From openjdk:8-jre-alpine
# copy jar from the first stage
COPY --from=builder target/my-app-1.0-SNAPSHOT.jar my-app-1.0-SNAPSHOT.jar
EXPOSE 8080
CMD ["java", "-jar", "my-app-1.0-SNAPSHOT.jar"]
Step 2: Authorize Container Registry to access the source code repository
Log on to the Container Registry console and authorize Container Registry to access the source code repository. In this example, Container Registry is authorized to access source code repositories on GitHub.
Step 3: Create an image repository
Step 4: Build an image
Result
- Check whether the image is built
On the management page of the Container Registry Personal Edition instance, choose Manage in the Actions column to go to the repository details page. In the left-side navigation pane, click Tags. On the Tags page, find the image that has been built.
in the left-side navigation pane. On the Repositories page, click the name of the repository or click - Check the number of layers in the image
Log on to the image repository on the Docker client. Run the relevant commands to pull the image and query the number of layers in the image. Only the JAR package compiled in the first stage is added to the image, which occupies small storage space, and the image contains four layers.
docker pull registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master docker inspect registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master | jq -s .[0][0].RootFS { "Type": "layers", "Layers": [ "sha256:f1b5933fe4b5f49bbe8258745cf396afe07e625bdab3168e364daf7c956b6b81", "sha256:9b9b7f3d56a01e3d9076874990c62e7a516cc4032f784f421574d06b18ef9aa4", "sha256:edd61588d12669e2d71a0de2aab96add3304bf565730e1e6144ec3c3fac339e4", "sha256:2be89a7ccd49878fb5f09d6dfa5811ce09fc1972f0a987bbb5a006992aa3dff3" ] }
- Run the image and check the result
Run the following command on the Docker client to run the image and check the result:
docker run -ti registry.cn-hangzhou.aliyuncs.com/docker-builder/java-multi-stage:master Hello World!