Chapter 04

镜像管理与优化

多阶段构建、镜像瘦身技巧、标签策略与安全扫描

多阶段构建(Multi-stage Build)

问题的起源

构建应用通常需要大量工具:编译器、构建系统、测试框架、开发依赖……但运行应用只需要最终产物。如果把所有工具都打包进镜像,镜像体积会非常大。

单阶段构建(Go 应用) FROM golang:1.21(800MB)+ 源码 + 编译 = 最终镜像 850MB。运行时根本用不到 Go 编译工具链。
多阶段构建(Go 应用) 阶段1:FROM golang:1.21 编译得到二进制文件。阶段2:FROM scratch 只复制二进制文件 = 最终镜像 10MB。

示例一:Go 应用多阶段构建


# === 构建阶段:使用完整 Go 工具链 ===
FROM golang:1.21-alpine AS builder

WORKDIR /build

# 先复制 go.mod 和 go.sum,利用层缓存
COPY go.mod go.sum ./
RUN go mod download

COPY . .

# CGO_ENABLED=0 生成静态二进制,GOOS=linux 确保跨平台兼容
RUN CGO_ENABLED=0 GOOS=linux go build -a -o server ./cmd/server

# === 运行阶段:使用空镜像 ===
FROM scratch

# 如需 HTTPS,需要 CA 证书
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

# 只复制编译好的二进制文件
COPY --from=builder /build/server /server

EXPOSE 8080
ENTRYPOINT ["/server"]

示例二:React 前端多阶段构建


# === 构建阶段 ===
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# === 生产运行阶段:Nginx 静态服务 ===
FROM nginx:1.25-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

基础镜像选择策略

基础镜像的选择直接影响镜像大小、安全性和维护成本:

镜像类型代表大小优点缺点
完整版 ubuntu:22.04 ~77MB 工具完整,调试方便 大,攻击面大
Slim 版 node:18-slim ~60MB 比完整版小,基于 Debian 缺少部分工具
Alpine node:18-alpine ~5MB 极小,安全 musl libc 兼容性问题
Distroless gcr.io/distroless/nodejs ~30MB 无 shell、无包管理器,最安全 调试极难
💡

Alpine 的兼容性问题 — Alpine 使用 musl libc 而非 glibc,某些原生模块(如 canvas、bcrypt 等 Node.js 模块)可能无法在 Alpine 上运行,或需要额外安装 python3makeg++ 重新编译。

镜像瘦身技巧全览

技巧一:RUN 命令合并


# ❌ 每条 RUN 创建一层,多余的缓存文件被分散保存
RUN apt-get update
RUN apt-get install -y curl git
RUN rm -rf /var/lib/apt/lists/*  # 这层根本无法删除前面层的文件!

# ✅ 合并为一层,清理在同一层完成
RUN apt-get update \
    && apt-get install -y curl git \
    && rm -rf /var/lib/apt/lists/*

技巧二:合理使用 .dockerignore

确保以下目录/文件不进入构建上下文(参见第3章示例)。

技巧三:选择合适基础镜像

优先选择 Alpine 或 Slim 版本,或使用多阶段构建的 scratch/distroless 作为最终运行镜像。

技巧四:多阶段构建

将构建工具链完全排除在最终镜像之外(参见上文示例)。

技巧五:不安装不必要的包


# 只安装生产依赖
RUN npm ci --only=production

# apt 不安装推荐包
RUN apt-get install -y --no-install-recommends curl

镜像标签策略

🚫

latest 标签的陷阱latest 只是一个普通标签,不代表"最新版"——除非你主动推送时打了这个标签。在生产环境使用 latest 意味着每次 docker pull 可能拉取不同版本,导致不可预期的行为。

推荐的标签策略:


# 语义化版本标签(推荐生产使用)
docker build -t myapp:1.2.3 .
docker build -t myapp:1.2 .
docker build -t myapp:1 .

# Git Commit SHA(完全不可变,适合 CI/CD 溯源)
docker build -t myapp:$(git rev-parse --short HEAD) .

# 可同时打多个标签
docker build \
  -t myapp:1.2.3 \
  -t myapp:latest \
  .
标签类型示例适用场景
语义化版本myapp:1.2.3生产部署,可回滚
Git SHAmyapp:a3f9d2cCI/CD 追踪,不可变
环境标签myapp:staging环境区分
分支标签myapp:feature-login开发测试
latestmyapp:latest仅用于开发/演示

镜像安全扫描

Docker Scout


# 扫描本地镜像的 CVE 漏洞
docker scout cves myapp:1.0

# 快速概览(摘要)
docker scout quickview myapp:1.0

# 与基础镜像对比
docker scout compare myapp:1.1 --to myapp:1.0

Trivy(Aqua Security 开源)


# 安装 Trivy
brew install trivy

# 扫描本地镜像
trivy image myapp:1.0

# 只显示高危和严重漏洞
trivy image --severity HIGH,CRITICAL myapp:1.0

docker build 命令参数详解


docker build \
  --tag myapp:1.0 \              # 镜像名和标签(简写 -t)
  --build-arg VERSION=1.0 \    # 传入 ARG 变量
  --target builder \            # 多阶段构建:只构建到 builder 阶段
  --platform linux/amd64,linux/arm64 \ # 跨平台构建(需要 buildx)
  --no-cache \                  # 禁用缓存,强制全新构建
  --pull \                      # 总是拉取最新基础镜像
  --file ./infra/Dockerfile \   # 指定 Dockerfile 路径(默认 ./Dockerfile)
  .                             # 构建上下文目录

跨平台构建(多架构镜像)


# 创建并使用 buildx builder(支持多平台)
docker buildx create --use --name multi-builder
docker buildx inspect --bootstrap

# 构建并推送 amd64 和 arm64 镜像
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t myapp:1.0 \
  --push \
  .

本章小结 — 多阶段构建是现代 Docker 最佳实践中最重要的技术,可将镜像从 GB 级缩减到 MB 级。合理的标签策略确保生产部署的可追溯性和稳定性。