⬆️ ⬇️

Multi-stage (multi-stage builds) builds in Docker

Docker starting from version 17.05 and higher began to support multi-stage builds (multi-stage builds). I was surprised to find that no one has yet written about this in Habré. So let's fix this gap.



Changes will be especially useful to those who collect images (images) on the basis of existing ones and who need to maintain their minimum size.



Everyone who compiled docker images knows that practically every instruction in the Dockerfile adds a separate layer and you need to clear this layer of all unnecessary artifacts before adding a new layer. Therefore, in order to create a truly effective Dockerfile, you have traditionally been offered to use scripts and other logic to maintain the lowest possible layer size. The usual practice was to use several Dockerfile depending on the purpose of the image assembly - one file for DEVELOPMENT with a specific set of tools for debugging, profiling and everything else and another image, much smaller in size for deploying an application to STAGING or PRODUCTION, with a set of components necessary for work applications.



Suppose we have a simple “hello world” HTTP-server, which we need to get together and run tests, and after that we collect the minimal docker image that contains only executable files.

')

An example can be taken from here.



Minimal Dockerfile will look like this.



Dockerfile:



FROM golang:latest COPY . . RUN go test && go build ./src/main.go 


Let's collect and run the image:



 docker image build -t hello_world:build . 


If you look at the image metadata: docker image inspect hello_world: build, you can see that it consists of 6 separate layers and takes about 800MB. And this is only Hello World, and what size a real application can have only one can imagine. Therefore, for PRODUCTION it already makes sense to assemble an image only from executable files.



As a result, you should run this sequence of commands:



Launch base container:



 docker container run -it --name hello_world hello_world:build 


Create a new container based on an existing one and copy binary files:



 docker create --name extract hello_world:build mkdir ./Production/ docker cp extract:/go/main ./Production/main docker rm -f extract docker rm -f hello_world 


Create a PRODUCTION container containing only the necessary files for the application to work:



 docker build --no-cache -t hello_world:latest ./Production/ rm ./Production 


The process may differ depending on your requirements, but in general it will somehow include similar steps.



So multi-stage builds allow you to greatly simplify this process and describe it inside the Dockerfile. Each FROM instruction can use an individual base image and each of them begins a new stage of assembling the docker image. But the main advantage is that you can copy the necessary artifacts from one stage to another. As a result, all of the above steps can be described like this.

Dockerfile:



 FROM golang:latest as build COPY . . RUN go build ./src/main.go FROM alpine:latest as production COPY --from=build /go/main . CMD ["./main"] 


And all you have to do is execute the command:



 docker image build -t hello_world:latest . 


Note: you should add separately that you can refer to the previous images using the alias specified in the FROM golang instruction: latest as build - as in the above example COPY - -from = build / go / main., And COPY --from = 0 / go / main.



References:



Source: https://habr.com/ru/post/349802/



All Articles