阿男的小窝

View the Project on GitHub

docker的multi stage build

dockermulti-stage build把「build」的「过程」和「结果」拆分开。

我们在build容器的时候,会在容器里面加入一些编译工具和依赖包,目的是为了得到编译后的结果。但是实际在后续的容器运行周期里面,并不再需要这些build过程中所依赖的工具,但是因为相关的软件包已经装入了容器,所以这些中间过程用到的工具极大地增加了build出来的image的尺寸。

因此,docker引入了multi-stage build的概念,把「build过程」和「build结果」拆分开。下面是配置文件的例子:

# First Stage
FROM golang:1.6-alpine

RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

# Second Stage
FROM alpine
EXPOSE 80
CMD ["/app"]

# Copy from first stage
COPY --from=0 /app/main /app

可以看到,上面的Dockerfile里面包含两个FROM,因此会build两个images。其中第一个FROM描述的容器,是用来使用golang的开发环境来build一个go语言写的程序。而第二个FROM所描述的容器,是使用第一个容器里面所build出来的程序,并运行这个程序用的。第一个容器的build过程如下:

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

上面这个命令会编译go写的程序。第二个容器使用第一个容器的build结果的方法如下:

COPY --from=0 /app/main /app

其中,/app/main是来自于第一个容器的build结果。--from=0,就是告知第二个容器,从第一个容器里面拷贝。其中0是第一个容器的索引编号。

这样,第二个用来运行程序的容器,就不需要安装go的开发环境了。我们可以在katacoda([Creating optimised Docker Images using Multi-Stage Builds Docker Katacoda](https://www.katacoda.com/courses/docker/multi-stage-builds))上面在线实验上面的例子,截图如下:

可以看到,用来build的容器为293mb,而运行用的容器只有12mb,因为它不需要保留编译过程中所需的go环境。从上面的结果可以看到,把build容器和runtime容器拆分开,可以大大精简最终的runtime容器的尺寸。docker提供的这个multi-stage build功能对于实际的生产和部署至关重要。