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 uses a Java application that is developed with Maven and uses GitHub to manage its source code repository as an example to describe how to build an image for a Java application by using Alibaba Cloud Container Registry and multi-stage builds.
Prerequisites
- The Alibaba Cloud 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 Code.
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 a Dockerfile to build an application image.
- 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, 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. In this way, the final image does not contain the source code.
- The final image has fewer layers and a smaller size.
The final image only contains a base image and compiled artifacts, which occupy few layers and require 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. This section uses GitHub as an example of the code repository hosting service.
Step 3: Create an image repository
Step 4: Build an image
Result
- Check whether the image is built
In the left-side navigation pane, choose Manage in the Actions column. In the left-side navigation pane, click Tags. On the Tags page, find the image that has been built.
. On the Repositories page, find the target image repository and 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. As shown in the returned result, 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!