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必要的产物,别整个目录 - 合理利用缓存:构建阶段尽量不变(依赖),复制源码放最后