1. 分阶段构建如何避免镜像臃肿?
原理:
- 阶段隔离:每个
FROM开始一个新阶段,前一阶段的文件默认不会进入下一阶段。 - 精准复制:通过
COPY --from=<stage>仅复制必要的文件(如编译后的二进制文件、依赖项),丢弃中间产物(如源码、临时文件、开发工具)。 - 轻量化最终镜像:最终阶段通常基于更小的基础镜像(如
alpine、scratch),仅包含运行所需的文件。
示例对比:
单阶段构建(臃肿)
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. 最佳实践:最大化分阶段构建的优势
- 最小化最终阶段的基础镜像:
- 使用
alpine、distroless或scratch(如 Go 静态二进制文件)。
- 使用
- 仅复制必要文件:
- 避免
COPY --from=... / /,明确指定路径(如/app/dist)。
- 避免
- 清理构建缓存:
- 在构建阶段末尾删除临时文件(如
apt-get purge -y gcc)。
- 在构建阶段末尾删除临时文件(如
- 合并相关命令:
- 减少层数(如
RUN apt-get update && apt-get install -y ...)。
- 减少层数(如
4. 验证镜像体积
通过 docker images 查看镜像大小,或使用工具分析:
docker build -t myapp .
docker images | grep myapp
# 或使用 dive 工具分析镜像层
dive myapp