15.7.4 多阶段构建与镜像瘦身
多阶段构建通过在同一 Dockerfile 中定义多个阶段,将编译、打包与运行环境分离,最终只保留运行所需的最小产物,显著降低镜像体积并减少攻击面。核心机制是 FROM <image> AS <name> 定义阶段,使用 COPY --from=<name> 拷贝产物实现构建与运行解耦。
安装与准备#
确保 Docker 可用(Linux):
sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl enable --now docker
docker version
多阶段构建示例(Go 应用)#
项目结构:
/opt/demo/
├── Dockerfile
├── main.go
└── .dockerignore
main.go:
package main
import "fmt"
func main() { fmt.Println("hello ops") }
.dockerignore(减少构建上下文):
.git
bin/
*.log
Dockerfile:
# Stage1: builder
FROM golang:1.21 AS builder
WORKDIR /src
COPY main.go .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app main.go
# Stage2: runtime
FROM gcr.io/distroless/static:nonroot
WORKDIR /app
COPY --from=builder /src/app /app/app
USER nonroot
ENTRYPOINT ["/app/app"]
构建与运行:
cd /opt/demo
docker build -t demo:multi .
docker run --rm demo:multi
预期效果:输出 hello ops,镜像体积显著小于 golang 基础镜像。
镜像瘦身关键命令与解释#
合并 RUN 指令、清理缓存(以 Debian/Ubuntu 为例):
RUN apt-get update \
&& apt-get install -y curl ca-certificates \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
apt-get clean与rm -rf /var/lib/apt/lists/*:删除缓存,减少层体积- 合并为一层:避免遗留无用层
查看镜像层与体积:
docker history demo:multi
docker images | grep demo
使用构建缓存提升效率(命中率实践):
# 依赖稳定的步骤放前面
COPY go.mod go.sum ./
RUN go mod download
# 变更频繁的代码放后面
COPY . .
RUN go build -o app
常见问题与排错#
-
构建失败:COPY --from 找不到文件
bash # 进入构建中间层调试(BuildKit) DOCKER_BUILDKIT=1 docker build --progress=plain -t demo:multi .
观察输出中的路径,确认COPY --from=builder /src/app是否正确。 -
运行时报错:no such file or directory
- 可能是二进制依赖 glibc 或架构不一致
- 使用静态编译或改用alpine/debian-slim运行镜像
修复示例:
Dockerfile
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app main.go
- 镜像仍然很大
- 检查.dockerignore是否排除node_modules/、target/等目录
- 检查是否使用了slim/distroless基础镜像
练习与验证#
-
练习 1:把 Node 应用改为多阶段
- 目标:构建阶段用node:18,运行阶段用node:18-slim
- 验证:对比docker images体积变化 -
练习 2:添加镜像体积门禁
bash SIZE=$(docker image inspect demo:multi --format='{{.Size}}') echo "Size: $SIZE bytes" test "$SIZE" -lt 50000000
预期:体积小于 50MB 时命令返回成功 -
练习 3:验证 .dockerignore
bash dd if=/dev/zero of=big.log bs=1M count=50 docker build -t demo:multi .
- 若.dockerignore包含*.log,构建速度与上下文体积应明显下降
小结#
多阶段构建将编译与运行隔离,结合 .dockerignore、合并层与瘦身镜像选择,可显著降低体积并提升安全性。建议在 CI 中加入镜像体积与漏洞扫描,形成持续优化闭环。