R / Richie全部文章 ↑

Linux · 2 分钟阅读

08. Dockerfile 多阶段构建

Docker 17.05+ 引入。一个 Dockerfile 多 FROM,编译环境和运行环境各占一个阶段,最终镜像只复制产物。

为什么要多阶段

  • 编译工具链不进最终镜像
  • 体积大幅缩小
  • 源码不会进最终镜像
  • 一次 build 完成,不需要脚本串接

传统做法的痛点

单 Dockerfile

FROM golang:1.9-alpine
RUN apk add --no-cache git ca-certificates
WORKDIR /go/src/app
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
    && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
WORKDIR /root/
CMD ["./app"]

编译依赖、源码、git 全部留在镜像里。go/helloworld:1 接近 300 MB。

多 Dockerfile + 脚本

写两个 Dockerfile,shell 脚本把第一个镜像里的二进制 docker cp 出来,塞给第二个 Dockerfile。

能跑,但脆弱。

多阶段一气呵成

# syntax=docker/dockerfile:1

FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o app

FROM alpine:3.20
RUN apk add --no-cache ca-certificates
COPY --from=builder /src/app /usr/local/bin/app
CMD ["app"]
docker build -t myapp:1.0 .

原文示例里的 golang:1.9-alpine / debian:stretch 都已 EOL,新项目用 1.22+ / bookworm 起步。

镜像对比:

镜像 大小
单阶段 ~295 MB
多阶段 ~15 MB

高级用法

指定到某阶段

docker build --target builder -t myapp:builder .

调试构建问题、做基础镜像分发时很有用。

跨镜像复制

COPY --from=nginx:1.27 /etc/nginx/nginx.conf /nginx.conf

--from=0 是"上一阶段";--from=builder 是命名阶段;--from=<image> 是外部镜像。

实践建议

  • 构建阶段放前面,运行阶段放后面
  • 用 AS <name> 命名阶段,可读性高
  • 阶段之间只 COPY 必要的产物,别整个目录
  • 合理利用缓存:构建阶段尽量不变(依赖),复制源码放最后

参考