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

Background information

Common issues in Docker image building

The image building service of Container Registry uses a Dockerfile to build the final image of an application. During this process, you may encounter the following issues:
  • 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

When you use multi-stage builds in a Dockerfile to build images for applications developed with compilation languages such as Java, you can enjoy the following benefits:
  • 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

In this example, a Java application that is developed with Maven and uses GitHub to manage its source code repository is used. Create a Dockerfile in the Java project and add the following configuration to the Dockerfile:
Note The Dockerfile includes two build stages, as shown in the following configuration.
  • 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.

  1. Log on to the Container Registry console.
  2. In the top navigation bar, select a region.
  3. In the left-side navigation pane, click Instances.
  4. On the Instances page, click the default instance.
  5. On the management page of the Container Registry Personal Edition instance, choose Repositories > Code Source in the left-side navigation pane.
  6. Click Bind Account for the GitHub service. In the GitHub dialog box, click Go to the source code repository to bind account. On the page that appears, enter your username and password to log on to GitHub.
  7. On the authorization page, click Authorize AliyunDeveloper.
    Authorization
    Go to the Code Source page. Check whether the status of the GitHub service appears as Bound.

Step 3: Create an image repository

  1. Log on to the Container Registry console.
  2. In the top navigation bar, select a region.
  3. In the left-side navigation pane, click Instances.
  4. On the Instances page, click the default instance.
  5. On the management page of the Container Registry Personal Edition instance, choose Repositories > Repositories in the left-side navigation pane. On the Repositories page, click Create Repository.
  6. Set the parameter as required.
    Repository configuration
    Parameter Description
    Region The region where the image repository resides. Container Registry supports 23 regions around the world.
    Namespaces The namespace to which the image repository belongs. An image repository belongs to only one namespace, whereas a namespace can contain multiple image repositories.
    Repository Name The name of the image repository.
    Repository Type The type of the image repository. Valid values: Public and Private. You can push images to an image repository only after you log on to the image repository, regardless of the repository type.
    • Public: You can pull images from the image repository without the need to log on to the image repository.
    • Private: You can pull images from the image repository only after you log on to the image repository on the Docker client.
    Summary The brief description of the image repository.
    Description The detailed description of the image repository.
  7. Click Next. In the Code Source step, specify the code source and build settings.
    • Code Source: the service or platform where the source code repository resides. Click the GitHub tab and select the source code repository that you have authorized Container Registry to access in Step 2: Authorize Container Registry to access the source code repository.
    • Build Settings: the mode in which images are built.
      1. Automatically Build Images When Code Changes: If you select this option, an image is automatically built when code is committed from a branch.
      2. Build With Servers Deployed Outside Chinese Mainland: If you select this option, images are built in a data center outside the Chinese Mainland and then pushed to the image repository.
      3. Build Without Cache: If you select this option, the system pulls the dependent base image for every image to be built. This may slow down the build process.
  8. Click Create Repository.
    On the Repositories page, check whether the image repository that you have created appears.

Step 4: Build an image

  1. On the management page of the Container Registry Personal Edition instance, choose Repositories > Repositories in the left-side navigation pane. On the Repositories page, click the name of the repository or click Manage in the Actions column to go to the repository details page.
  2. In the left-side navigation pane, click Build. On the page that appears, click Add Build Rule in the Build Rules section.
  3. In the Add Build Rule dialog box, set the parameters as required.
    Add Build Rule
    Parameter Description
    Type The type of content pushed to the source code repository that will trigger the build rule. Valid values: Branch and Tag.
    Branch/Tag The code branch or tag that will trigger the build rule.
    Dockerfile Directory The directory where the Dockerfile resides. The specified directory is a relative directory, with the root directory of the code branch as its parent directory.
    Dockerfile Filename The name of the Dockerfile. Default value: Dockerfile.
    Image Tag The tag of the image to be built.
  4. Click Confirm.
  5. In the Build Rules section, find the created rule and click Build in the Actions column.
    After you start the build, a build record is generated in the Build Log section. When the status of the build record becomes Successful, the image is built.

Result

  • Check whether the image is built

    On the management page of the Container Registry Personal Edition instance, choose Repositories > Repositories in the left-side navigation pane. On the Repositories page, click the name of the repository or click 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.

  • 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!