Skip to content

03 · 镜像构建进阶:分层、多阶段、体积优化 ​

D01 讲了"怎么写出能跑的 Dockerfile",本篇讲"怎么把镜像构建快、体积小、更安全"。三层能力:看图层、控体积、防泄漏。先给结论:镜像越小,拉取越快、攻击面越小、构建缓存越容易被复用。

速览卡 ​

项内容
一句话定位镜像体积、构建缓存与安全性的进阶三板斧
核心内容图层心智、.dockerignore、多阶段构建、体积优化、镜像安全
读完能回答为什么镜像要分层?多阶段构建解决了什么?怎么把 1GB 压到 200MB?
相关板块D01 · Docker | D02 · 部署 CI/CD | C17 · 部署

一、先给结论:三个数字的优化 ​

text
构建时间:依赖层命中缓存 → 秒级跳过
镜像体积:只留运行所需 → 几百 MB 变几十 MB
安全面:不带源码/密钥/调试器 → 漏洞更少

一句话:Dockerfile 的顺序、上下文和阶段划分,决定了这三项。先从"慢"和"大"开始优化,安全随之而来。


二、图层心智:为什么顺序决定缓存 ​

2.1 每一层都是文件系统快照 ​

RUN / COPY 每执行一条就生成一层。构建缓存按层命中:前面没变,后面直接复用。

dockerfile
# ❌ 慢:代码在最前,每次改动都重装依赖
COPY . .
RUN pip install -r requirements.txt

# ✅ 快:依赖锁定在前,代码在后
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

2.2 缓存失效的精确度 ​

写法缓存行为
COPY . .任意文件变化 → 该层起全部重建
COPY requirements.txt .只有依赖文件变化才重建依赖层
RUN apt-get update 无锁永远重新跑,且可能拿到不同结果

前端类比:像 npm install 与 node_modules 缓存——只要 package-lock.json 没变,就不该重装。


三、.dockerignore:先缩小构建上下文 ​

dockerfile
# .dockerignore
.git
node_modules
dist
*.md
.env
作用说明
缩小上下文COPY . . 只打包必要文件,构建更快
防泄漏.env、密钥、历史数据不进镜像
防污染排除本机 node_modules/__pycache__ 覆盖容器内文件

铁律:.gitignore 拦不住的密钥,用 .dockerignore 再拦一次。镜像会被拉取、被扫描,任何进过镜像的敏感信息都算泄露。


四、多阶段构建:编译产物与运行环境分离 ​

dockerfile
# 阶段 1:构建(带全量工具链)
FROM node:20 AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build

# 阶段 2:运行(只留产物)
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package*.json ./
RUN npm ci --omit=dev
CMD ["node", "server.js"]
好处说明
体积小编译器、测试工具不进入最终镜像
更安全构建工具链的漏洞不带到线上
更清晰构建与运行依赖彻底分离

Python 对照:构建阶段装 build-essential 编译扩展,运行阶段只留编译好的 .so 与 --omit=dev 依赖。


五、体积优化清单 ​

手段效果注意
用 slim/distroless 基础镜像砍掉 shell/包管理器distroless 不方便进容器调试
多阶段只拷贝产物避免工具链残留保证 COPY --from 路径正确
清理安装缓存apt-get clean、rm -rf ~/.cache要在同一 RUN 里做
依赖去 devnpm ci --omit=dev / pip --no-dev别把测试框架带进镜像
压缩产物静态资源 gzip/br减少分层体积

判断顺序:先看每层多大(docker history),最大层优先处理,别在 10MB 的层上抠。


六、镜像安全:不只是体积 ​

风险做法
以 root 运行创建非 root 用户,USER app
密钥进镜像构建期注入(--secret),运行期环境变量
带调试器/源码多阶段只拷运行产物
基础镜像漏洞锁定 tag、定期 docker scan/SCA 扫描
无固定版本FROM python:3.12-slim 比 :latest 可复现

与 S07 的关系:镜像里的依赖也要走 S07 供应链扫描,docker scan 就是容器层的 SCA。


七、自检清单 ​

  • [ ] Dockerfile 把"不变的依赖"放前、"易变的代码"放后
  • [ ] 有 .dockerignore,且确认不含密钥/.git
  • [ ] 用多阶段构建,运行镜像不含编译工具链
  • [ ] 基础镜像锁版本,用 slim/distroless 且知道取舍
  • [ ] 容器内非 root 运行,密钥不落镜像
  • [ ] CI 里跑了镜像漏洞扫描

八、常见误区 ​

误区事实
"代码放前面也没事"一行代码改动会连带重装依赖,构建慢几倍
".gitignore 就够了"镜像上下文是另一条泄露路径,要 .dockerignore 兜底
"多阶段只是为了体积"体积小、攻击面小、依赖边界清晰是三重收益
"slim 一定最好"distroless 无 shell,调试和生产排障要换思路
"镜像小就安全"运行用户、密钥、基础镜像漏洞同样决定安全性

九、动手练习 ​

练习 A:看你的层 ​

对自己项目跑 docker build,再用 docker history <镜像> 找出体积最大的三层,记录占比。

练习 B:压体积 ​

把现有镜像改成多阶段 + .dockerignore,记录优化前后体积和构建时间,目标至少减半。

练习 C:非 root 化 ​

给一个容器加上 USER 指令与最小权限,验证容器内进程不再是 root,并确认服务仍正常。


相关文档 ​

本区 ​

其他域 ​