1. Home
  2. Training Library
  3. Microsoft Azure
  4. Courses
  5. Building Containers with Azure DevOps

Multi-Stage Builds


Course Intro
Course Conclusion

The course is part of these learning paths

Microsoft Azure for Solution Architects
AZ-400 Exam Prep: Microsoft Azure DevOps Solutions
more_horizSee 2 more
Start course

This course is designed to give you a solid understanding of containers and how they are used in Azure DevOps. It begins by looking at creating deployable images through Docker containers, microservices, and at the various container-related services available in Azure, including Azure Container Instances, the Azure Kubernetes Service, the Azure Container Registry, Azure Service Fabric, and Azure App Service.

The course also looks at Dockerfile and Docker multi-stage builds before finishing with a hands-on demonstration that shows you how to create an Azure Container Registry.

For any feedback relating to this course, please contact us at support@cloudacademy.com

Learning Objectives

  • Learn about Docker and its role in deploying containerized apps
  • Understand how microservices can be used for deploying apps
  • Learn about the container-related services available in Azure
  • Learn about using multi-stage builds when working with Docker
  • Gain a practical understanding of how to create an Azure Container Registry
  • Gain a practical understanding of how to add Docker support to an application

Intended Audience

This course is intended for DevOps professionals who wish to learn how to use containers to design and implement strategies for developing application code and infrastructure that allow for continuous integration, testing, delivery, monitoring, and feedback.


To get the most from this course, you should have a basic understanding of the Azure platform and of container concepts.


Hi there. Welcome to “Multi-Stage Builds”. In this lecture, we’re going to take a look at what multi-stage builds are and what they bring to the table.

So, Multi-stage builds are a new feature that makes life easier when working with Dockerfiles. They are extremely helpful when trying to optimize Dockerfiles while ensuring that they are still easy to read.

Prior to the introduction of multi-stage builds, what you would typically have is one Dockerfile to use for development. This Dockerfile would contain everything that you need to build the application that you wished to deploy. You’d also have a second, slimmed-down Dockerfile that you would use for production. This second file would contain just the application and only the resources needed to run it. This “builder pattern” of maintaining two Dockerfiles, obviously, isn’t ideal.

The code that you see on your screen is a good example of what the typical “builder pattern” consists of. Notice we have 3 different files. We have DockerFile.build, the Dockerfile, and build.sh.


FROM golang:1.7.3

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html  

COPY app.go .

RUN go get -d -v golang.org/x/net/html \

  && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .


FROM alpine:latest  

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY app .

CMD ["./app"]  



echo Building alexellis2/href-counter:build

docker build --build-arg https_proxy=$https_proxy --build-arg http_proxy=$http_proxy \  

    -t alexellis2/href-counter:build . -f Dockerfile.build


docker create --name extract alexellis2/href-counter:build  

docker cp extract:/go/src/github.com/alexellis/href-counter/app ./app  

docker rm -f extract

echo Building alexellis2/href-counter:latest

docker build --no-cache -t alexellis2/href-counter:latest .

rm ./app

If you look at this example, you’ll see that it artificially compresses two different RUN commands together. This is done to avoid creating an additional layer in the image. As is the case with any sort of coding, the more code you have, the more error-prone it becomes – and the more difficult it becomes to maintain.

When the build.sh script is run, it first has to build the first image. Then, it needs to create a container from it, so that it can copy the artifact out, before building the second image. In this scenario, you are left with 2 images – both of which take up room on your system. You are also left with the app artifact on your local disk as well.

Enter Multi-stage builds, which greatly simplify things.

Prior to multi-stage builds, keeping image sizes down was a challenge. This is because every instruction included within a Dockerfile adds a layer to the image. Not only that, but you also need to remember to clean up unneeded artifacts before moving on to the next layer. 

Until multi-stage builds became available, authoring an efficient Dockerfile meant using shell tricks and other logic to keep the layers as small as possible. You also had to use the same tricks to ensure that each layer has only the artifacts that it needs from the previous layer and nothing else.

On the screen is an example of a multi-stage file.

FROM golang:1.7.3

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html  

COPY app.go .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM alpine:latest  

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=0 /go/src/github.com/alexellis/href-counter/app .

CMD ["./app"]  

When you leverage multi-stage builds, you can use multiple FROM statements within the Dockerfile. Each of the FROM statements begins a new stage. The stages, themselves, are numbered in order, starting with stage 0.  What you would typically do, though, to make the Dockerfile easier to maintain, is use the AS clause to name, or alias, each stage.

Notice the aliasing that’s been added to the file on your screen:

FROM golang:1.7.3 AS builder

WORKDIR /go/src/github.com/alexellis/href-counter/

RUN go get -d -v golang.org/x/net/html  

COPY app.go    .

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .


FROM alpine:latest  

RUN apk --no-cache add ca-certificates

WORKDIR /root/

COPY --from=builder /go/src/github.com/alexellis/href-counter/app .

CMD ["./app"]  

This example on your screen names the stage and uses the name in the COPY instruction. By referencing the name in the copy instruction, even if the instructions in this Dockerfile are re-ordered later on for some reason, the COPY won’t break.

I should also mention that when you build an image, you don’t have to build the entire Dockerfile, including every stage. Instead, you can specify a single target build stage. The command that you see on your screen, when using our example Dockerfile, stops at the stage named builder:

$ docker build --target builder -t alexellis2/href-counter:latest

The –target option, in this command, tells docker build to create an image up to the target of builder, which is a named stage in our example file. 

In the next lesson, we’ll take a look at some best practices that you can follow when working with multi-stage builds.

About the Author
Thomas Mitchell
Learning Paths

Tom is a 25+ year veteran of the IT industry, having worked in environments as large as 40k seats and as small as 50 seats. Throughout the course of a long an interesting career, he has built an in-depth skillset that spans numerous IT disciplines. Tom has designed and architected small, large, and global IT solutions.

In addition to the Cloud Platform and Infrastructure MCSE certification, Tom also carries several other Microsoft certifications. His ability to see things from a strategic perspective allows Tom to architect solutions that closely align with business needs.

In his spare time, Tom enjoys camping, fishing, and playing poker.