前言
多阶段构建(Multi-stage builds)是从 Docker 17.05 版本开始引入的功能。这个功能允许在单个 Dockerfile 中定义多个构建阶段,并且在最终镜像中只包含所需的内容,从而减小镜像的大小。通过多阶段构建,可以将构建环境和运行环境分开,使得最终的镜像更加轻量化,减少了不必要的依赖和文件
使用多阶段构建
# syntax=docker/dockerfile:1
FROM golang:1.21
WORKDIR /src
COPY <<EOF ./main.go
package mainimport "fmt"func main() {fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.goFROM scratch
COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]
如上例子,可以有两个或多个 FROM 关键字,第一个 FROM 阶段作的事相当于把程序编译成一个二进制文件,第二个 FROM 阶段从第一阶段拷贝编译后的产物,这样最终生成镜像的体积,因为丢弃了编译阶段的依赖了,就小了很多
命名构建阶段
上面的例子,因为没有设置别名,默认情况 docker 内部会给每一个 FROM 按顺序从 0 开始自增设置编号,所以我们用了 COPY --from=0 /bin/hello /bin/hello这样的方式来引用,这样缺点在于,如果构建阶段的顺序发生了变化,那么我们引用的编号也要修改,这样就不够优雅了,好在支持设置别名功能,将上面的例子改造后如下:
# syntax=docker/dockerfile:1
FROM golang:1.21 as build
WORKDIR /src
COPY <<EOF /src/main.go
package mainimport "fmt"func main() {fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.goFROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]
构建部分阶段
多阶段构建,支持构建部分 FROM 的内容,这样可以方便调试,比如我想看下编译后的内容,就没必要每次还得执行编译后的其他阶段,使用方法如下:
docker build --target build -t hello .
从外部镜像作拷贝数据
前面我们都是从上一个 FROM 阶段拷贝的数据,假如我已经有现成的镜像了,在使用 FROM 拷贝就略显得臃肿了,好在 COPY --from 指令支持从单独的镜像(本地或远程)里面拷贝数据,使用如下:
COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf
这个功能非常有用,假如我们应用镜像运行时依赖多个中间件才能正常工作,那么传统的做法需要做一个大而全的基础镜像,通过这种方式我们就可以组合出更灵活按需适配的场景,从而大幅度减小优化镜像的体积
使用上一阶段作为新阶段
这个很简单,就是可以使用上一阶段构建完的结构,作为新的阶段的起步依赖,例子如下:
# syntax=docker/dockerfile:1FROM alpine:latest AS builder
RUN apk --no-cache add build-baseFROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cppFROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp
BuildKit和普通的builder的区别
BuildKit 于 Docker 18.09 版本开始引入。BuildKit 旨在提供更快速、更灵活、更安全的构建体验,以及更好的多阶段构建支持。
BuildKit 仅仅构建其依赖的前置,而 builder 则会构建所有依赖,如下例子:
# syntax=docker/dockerfile:1
FROM ubuntu AS base
RUN echo "base"FROM base AS stage1
RUN echo "stage1"FROM base AS stage2
RUN echo "stage2"
激活 BuildKit 后,仅仅需要构建 base和 stage2 即可
DOCKER_BUILDKIT=1 docker build --no-cache -f Dockerfile --target stage2 .
没激活 BuildKit 后,则需要构建 base,stage1 和 stage2
DOCKER_BUILDKIT=0 docker build --no-cache -f Dockerfile --target stage2 .
总结
使用多阶段构建可以帮助优化和简化容器镜像的构建过程,减小镜像大小,简化构建流程,提升构建灵活性,加速构建速度,优化镜像层次结构,减少依赖关系,从而提高了容器化应用程序的部署效率和性能