多阶段构建是一个新特性,须要 Docker 17.05 或更高版本的守护进程和客户端。对于那些努力优化 Dockerfiles 并使其易于阅读和维护的人来讲,多阶段构建很是有用。html
构建镜像时最具挑战性的事情之一就是缩小镜像大小。Dockerfile 中的每一条指令都会在镜像中添加一个层,在进入下一层以前,您须要记住清除全部不须要的工件。要编写一个真正高效的 Dockerfile,传统上须要使用 shell 技巧和其余逻辑来保持层尽量小,并确保每一层都有它须要的来自前一层的工件,而没有其余东西。linux
实际上,有一个 Dockerfile 用于开发环境(包含构建应用程序所需的全部内容),同时有一个精简的 Dockerfile 用于生产环境(仅包含应用程序和运行应用程序所需的内容)是很是常见的。这被称为“建造者模式”。维护两个 Dockerfiles 并不理想。nginx
这里有一个例子 Dockerfile.build
文件以及符合上述建造者模式的 Dockerfile
:git
Dockerfile.build
:github
FROM golang:1.7.3 WORKDIR /go/src/github.com/alexellis/href-counter/ 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 .
请注意,此示例还使用 Bash 操做符 &&
将两个 RUN
命使人为压缩在一块儿,以免在镜像中建立额外的层。这很容易发生故障,也很难维护。例如,很容易插入另外一个命令而忘记使用 \
字符继续行。golang
Dockerfile
:docker
FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY app . CMD ["./app"]
build.sh
:shell
#!/bin/sh 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 container create --name extract alexellis2/href-counter:build docker container cp extract:/go/src/github.com/alexellis/href-counter/app ./app docker container rm -f extract echo Building alexellis2/href-counter:latest docker build --no-cache -t alexellis2/href-counter:latest . rm ./app
当你运行 build.sh
脚本,它须要构建第一个镜像,从中建立一个容器来复制工件,而后构建第二个镜像。这两个镜像在您的系统上占用空间,而且您的本地磁盘上仍然有 app
工件。app
多阶段构建极大地简化了这种状况!工具
对于多阶段构建,能够在 Dockerfile 中使用多个 FROM
语句。每一个 FROM
指令均可以使用不一样的基镜像,而且它们都开始了构建的新阶段。您能够选择性地将工件从一个阶段复制到另外一个阶段,舍弃在最终镜像中您不想要的全部内容。为了说明这是如何工做的,让咱们使用多阶段构建调整前一节中的 Dockerfile。
Dockerfile
:
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"]
您只须要一个 Dockerfile。您也不须要单独的构建脚本。只要运行 docker build
。
$ docker build -t alexellis2/href-counter:latest .
最终的结果是与前面相同的微小生产镜像,而且显著下降了复杂性。您不须要建立任何中间镜像,也不须要将任何工件提取到本地系统中。
它是如何工做的?第二个 FROM
指令用 alpine:latest
镜像做为基础,开始一个新的构建阶段。COPY --from=0
行只将前一阶段的构建工件复制到这个新阶段。Go SDK 和任何中间工件都会被留下,不会保存在最终的镜像中。
默认状况下,没有对阶段进行命名,能够经过它们的整数来引用它们,FROM
指令的第一个整数从 0 开始。可是,您能够经过添加一个 AS <NAME>
到 FROM
指令来命名阶段。下面示例经过命名阶段并在 COPY
指令中使用名称改进了前面一个示例。这意味着,即便 Dockerfile 中的指令稍后被从新排序,COPY
也不会破坏。
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"]
在构建映像时,没必要构建包括每一个阶段的整个 Dockerfile。你能够指定目标构建阶段。如下命令假设你正在使用以前的 Dockerfile
,可是在名为 builder
的阶段中止:
$ docker build --target builder -t alexellis2/href-counter:latest .
这可能很是强有力的几个场景是:
调试(debug)
阶段和一个精益的 生产(production)
阶段测试(testing)
阶段,在这个阶段你的应用会被测试数据填充,可是在构建产品时,使用一个使用真实数据的不一样阶段。当使用多阶段构建时,您不受限于从 Dockerfile 中先前建立的阶段进行复制。您可使用 COPY --from
指令从单独的镜像中进行复制,可使用本地镜像名称、本地或 Docker 注册表上可用的标签或标签 ID。Docker 客户端会在必要时拉取镜像并从中复制工件。语法是:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
在使用 FROM
指令时,您能够引用前一阶段的内容。例如:
FROM alpine:latest as builder RUN apk --no-cache add build-base FROM builder as build1 COPY source1.cpp source.cpp RUN g++ -o /binary source.cpp FROM builder as build2 COPY source2.cpp source.cpp RUN g++ -o /binary source.cpp