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 cleanrm -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

常见问题与排错#

  1. 构建失败:COPY --from 找不到文件
    bash # 进入构建中间层调试(BuildKit) DOCKER_BUILDKIT=1 docker build --progress=plain -t demo:multi .
    观察输出中的路径,确认 COPY --from=builder /src/app 是否正确。

  2. 运行时报错:no such file or directory
    - 可能是二进制依赖 glibc 或架构不一致
    - 使用静态编译或改用 alpine/debian-slim 运行镜像

修复示例:
Dockerfile RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app main.go

  1. 镜像仍然很大
    - 检查 .dockerignore 是否排除 node_modules/target/ 等目录
    - 检查是否使用了 slim/distroless 基础镜像

练习与验证#

  1. 练习 1:把 Node 应用改为多阶段
    - 目标:构建阶段用 node:18,运行阶段用 node:18-slim
    - 验证:对比 docker images 体积变化

  2. 练习 2:添加镜像体积门禁
    bash SIZE=$(docker image inspect demo:multi --format='{{.Size}}') echo "Size: $SIZE bytes" test "$SIZE" -lt 50000000
    预期:体积小于 50MB 时命令返回成功

  3. 练习 3:验证 .dockerignore
    bash dd if=/dev/zero of=big.log bs=1M count=50 docker build -t demo:multi .
    - 若 .dockerignore 包含 *.log,构建速度与上下文体积应明显下降

小结#

多阶段构建将编译与运行隔离,结合 .dockerignore、合并层与瘦身镜像选择,可显著降低体积并提升安全性。建议在 CI 中加入镜像体积与漏洞扫描,形成持续优化闭环。