docker镜像体积优化-分段构建

108 阅读2分钟

1. 分阶段构建如何避免镜像臃肿?

原理:

  • 阶段隔离:每个 FROM 开始一个新阶段,前一阶段的文件默认不会进入下一阶段。
  • 精准复制:通过 COPY --from=<stage> 仅复制必要的文件(如编译后的二进制文件、依赖项),丢弃中间产物(如源码、临时文件、开发工具)。
  • 轻量化最终镜像:最终阶段通常基于更小的基础镜像(如 alpinescratch),仅包含运行所需的文件。

示例对比:

单阶段构建(臃肿)
FROM node:16
WORKDIR /app
COPY . .
RUN npm install && npm run build  # 包含源码、devDependencies、构建工具
CMD ["node", "dist/app.js"]       # 实际只需 dist/ 目录
  • 问题:镜像包含 node_modules(含开发依赖)、源码、构建工具,体积可能超过 1GB。
分阶段构建(精简)
# 阶段1:构建(包含完整环境)
FROM node:16 AS builder
WORKDIR /app
COPY package.json .
RUN npm install                   # 安装所有依赖(含 devDependencies)
COPY . .
RUN npm run build                 # 生成 dist/ 目录

# 阶段2:运行(仅保留必要文件)
FROM node:16-alpine              # 使用更小的基础镜像
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json .
RUN npm install --production      # 仅安装生产依赖
CMD ["node", "dist/app.js"]
  • 优势:最终镜像基于 alpine,仅包含 dist/ 和运行依赖,体积可能仅 200MB。

2. 可能导致臃肿的误用场景

分阶段构建本身不会导致臃肿,但以下错误用法会抵消其优势

误用1:无意义的阶段划分

FROM ubuntu AS stage1
RUN apt-get install -y gcc       # 安装编译工具

FROM ubuntu AS stage2
COPY --from=stage1 / /           # 错误!复制了整个阶段1的系统文件
  • 结果:阶段2仍然包含阶段1的所有文件,镜像臃肿。
  • 修复:仅复制需要的文件(如 /usr/local/bin/myapp)。

误用2:未清理临时文件

FROM node:16 AS builder
RUN npm install && npm run build
# 未删除 node_modules 或缓存

FROM node:16-alpine
COPY --from=builder /app /app    # 复制了未清理的 node_modules
  • 结果node_modules 中的开发依赖被带入最终镜像。
  • 修复:在构建阶段结束后清理缓存:
    RUN npm install && npm run build && rm -rf node_modules
    

3. 最佳实践:最大化分阶段构建的优势

  1. 最小化最终阶段的基础镜像
    • 使用 alpinedistrolessscratch(如 Go 静态二进制文件)。
  2. 仅复制必要文件
    • 避免 COPY --from=... / /,明确指定路径(如 /app/dist)。
  3. 清理构建缓存
    • 在构建阶段末尾删除临时文件(如 apt-get purge -y gcc)。
  4. 合并相关命令
    • 减少层数(如 RUN apt-get update && apt-get install -y ...)。

4. 验证镜像体积

通过 docker images 查看镜像大小,或使用工具分析:

docker build -t myapp .
docker images | grep myapp
# 或使用 dive 工具分析镜像层
dive myapp