Skip to content

Instantly share code, notes, and snippets.

@mkfares
Created August 17, 2020 10:52
Show Gist options
  • Save mkfares/2b483195ce51fcf4bdd908ee6484bb2e to your computer and use it in GitHub Desktop.
Save mkfares/2b483195ce51fcf4bdd908ee6484bb2e to your computer and use it in GitHub Desktop.
Docker Images - Multistage Builds

Docker Images - Multistage Builds

A multistage build refers to building docker images in multiple steps. It is based on a dockerfile that includes multiple FROM instructions.

An example of multistage build consists of creating an image with all the development tools to build an application. The second stage consists of copying the compiled application binaries with the necessary runtime tools to a new image.

Multistage builds have many advantages:

  • Maintain a single docker file to build multiple images
  • Avoid using scripts to create related images
  • Copy and reuse artifacts from one stage to another stage
  • Allow the built of a specific target in a dockerfile
  • Reduce the final image size

Name a stage

Stages are named to be referenced later in the dockerfile. The "AS" keyword is appended to the FROM instruction to name the current stage. The stage name can be used in the subsequent FROM or COPY instructions.

FROM progres:latest AS build

Specify the source image of a stage

A stage can be based on an external image or on a previous stage.

FROM alpine:latest AS build
...
FROM build AS dev

Copy artifacts

In multistage dockerfile, you can copy artifacts such as files and directories from previous stages into another stage. Also, you are allowed to copy from external images into the current stage.

The COPY instruction has the --from flag that accepts a stage name or an external image. The syntax of the COPY instruction is as follows:

COPY --from=<stage-name> <source> <destination>
COPY --from=<image-name> <source> <destination>
COPY --from=build /release /app/publish
COPY --from=alpine /data/docs /app/doc

Build a specific stage

With a multistage dockerfile, you can build a specific stage instead of building all the stages. This is achieved using the --target flag in the docker build command.

$ docker build --target <stage-name> ...
$ docker build --target build -t myapp-dev:1.2 .

In case of no target is specified during the build, the last stage in the dockerfile is built. Usually, a dockerfile ends with a default or final stage as follows:

FROM alpine:lates AS build
...

FROM build AS production
...

FROM production AS default

Example of a multistage dockerfile

The following code is an example of multistage dockerfile generated by the Docker extension in Visual Studio Code. This dockerfile builds and publishes a "Hello World" console application.

# Stage 1
FROM mcr.microsoft.com/dotnet/core/runtime:3.1 AS base
WORKDIR /app

# Stage 2
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["MyApp.csproj", "./"]
RUN dotnet restore "./MyApp.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "MyApp.csproj" -c Release -o /app/build

# Stage 3
FROM build AS publish
RUN dotnet publish "MyApp.csproj" -c Release -o /app/publish

# Stage 4
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Stage 1:

  1. The runtime image mcr.microsoft.com/dotnet/core/runtime:3.1 is pulled from the Microsoft Container Repository and given the name "base". This image is used as the base image to publish our console application.

Stage 2:

  1. The SDK image mcr.microsoft.com/dotnet/core/sdk:3.1 is pulled from the Microsoft repository and named "build". This image is used to compile and build our application.
  2. The project file is copied to the /src folder inside the build image.
  3. The dotnet restore command downloads all dependencies referenced in the project file.
  4. The project files are copied to the /src folder inside the build image.
  5. The project is built to create a and application release binaries. The release binaries are stored in folder /app/build.

Stage 3:

  1. The image of this stage is based on the "build" image from stage 2. The name of the stage is set to "publish".
  2. Publish the application binaries to the /app/publish folder.

Stage 4:

  1. The image used in this stage is based on the "base" image (stage 1) which includes the runtime tools. The stage is named "final".
  2. Copy the content of the folder /app/publish from stage 3 to the folder /app/publish in the "base" image.
  3. Run the application when the container starts.

Build images

To build the final image destined for production:

$ docker build -t myapp .

The generated images are listed below:

$ docker images
REPOSITORY                              TAG        SIZE
myapp                                   latest     190MB
<none>                                  <none>     709MB
mcr.microsoft.com/dotnet/core/sdk       3.1        708MB
mcr.microsoft.com/dotnet/core/runtime   3.1        190MB

Compare the size of the myapp image (final image) with the sdk image (build image). The <none> image corresponds to stage 3 image (publish image).

The myapp container can be run as follow:

$ docker run --rm myapp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment