暗色模式

Docker 镜像瘦身实战:多阶段构建从 1.6GB 到 184MB

技术教程
2026-08-04
23
0

Docker 镜像瘦身实战:多阶段构建从 1.6GB 到 184MB

同样的 Python 应用,一种 Dockerfile 写法产出的镜像 1.62GB,另一种只要 184MB——差了 8.8 倍。这不是玄学,而是多阶段构建(Multi-stage Build)的功劳。本文用一个 Flask 应用全程实测,带你从原理到实战,把镜像体积打下来。

一、镜像为什么会膨胀

先看最常见的"新手 Dockerfile"长什么样:

FROM python:3.12
WORKDIR /app
COPY app.py .
RUN pip install --no-cache-dir flask
CMD ["python", "app.py"]

问题出在基础镜像:python:3.12全量镜像,里面装着 Python 编译器、头文件、开发工具、文档、pip 缓存……这些在运行阶段完全用不到的东西,全被塞进了最终镜像。

所以一个只跑几行 Flask 的应用,镜像却轻松超过 1.5GB——99% 是"搬家费"。

二、单阶段构建:问题现场

在演示服务器上真实构建一次(Dockerfile.single,如上代码),结果:

单阶段与多阶段 Dockerfile 对比

单阶段镜像 1.62GB,而下面这个多阶段版本只有 184MB

FROM python:3.12 AS builder
WORKDIR /app
RUN pip install --prefix=/install --no-cache-dir flask

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY app.py .
CMD ["python", "app.py"]

两张 Dockerfile 的差别就是这篇文章的全部答案。

三、多阶段构建:原理与实战

核心原理

多阶段构建允许一个 Dockerfile 里写多个 FROM,每个 FROM 开启一个独立阶段,只有最后一个阶段会成为最终镜像:

  • builder 阶段FROM python:3.12 AS builder):用全量镜像,负责装依赖、编译——需要完整工具链;
  • runtime 阶段FROM python:3.12-slim):用最小基础镜像,通过 COPY --from=builder /install /usr/local 只把安装好的产物拷过来,编译器、缓存、头文件一律不带走。

AS builder 给阶段起别名,COPY --from=builder 引用它——阶段名让 Dockerfile 更清晰,也避免写死顺序。

完整构建过程

--no-cache 强制完整构建,8 个步骤一目了然:

多阶段构建的 8 步完整输出

关键看三步:

  1. Step 1/8builder 阶段启动——FROM python:3.12 AS builder,全量镜像登场;
  2. Step 3/8RUN pip install --prefix=/install --no-cache-dir flask 安装依赖(flask-3.1.3 及其 7 个依赖),产物装在 /install 前缀下;
  3. Step 4/8切到 runtime 阶段——FROM python:3.12-slim,第二个阶段开始;
  4. Step 6/8COPY --from=builder /install /usr/local——只把装好的包拷进新镜像,其余全部丢弃。

效果对比与运行验证

镜像大小对比与运行验证

  • app-single:1.62GB vs app-multi:184MB,体积缩小 88.6%
  • 多阶段镜像启动容器、curl 访问返回 hello from multi-stage build——功能完全一致,瘦身不缩水。

四、配套的最佳实践

多阶段构建是最大的一块,但只靠它还不够,下面几个习惯叠加使用效果更佳。

1. .dockerignore:别把秘密装进镜像

构建上下文里被 COPY 进镜像的文件,会永久留在镜像层里——.env、密钥一旦进去,就等于公之于众(镜像会推送到仓库)。.dockerignore.gitignore 写法一致:

.git/  __pycache__/  *.pyc
node_modules/  .venv/
.env  .env.local  secrets/
dist/  build/  *.egg-info/
Dockerfile  docker-compose.yml

2. 依赖安装顺序:让缓存最大化命中

Docker 对每一条指令做缓存,只要指令输入没变就用缓存。把"变化少的"放前面:

COPY requirements.txt .      # 1. 依赖清单(很少变)
RUN pip install -r requirements.txt   # 2. 装依赖(清单没变就命中缓存)
COPY app.py .                # 3. 源码(经常变,只失效这一层)

写反了的话(先 COPY 源码再装依赖),每次改一行代码都要重新下载安装全部依赖。

3. 基础镜像怎么选

基础镜像大小适用场景
python:3.12~1GB仅限构建阶段
python:3.12-slim~150MB默认运行镜像(glibc,兼容绝大多数 wheel)
python:3.12-alpine~55MB纯 Python 应用;小心 musl 坑(NumPy/Pandas 等无 musllinux wheel 会现场编译,又慢又容易失败)
scratch / distroless0~50MB静态编译语言(Go/Rust);无 shell 不能 docker exec 调试

4. 安全与整洁

  • 非 root 运行RUN useradd -m appuser && USER appuser,别让业务进程跑在 root 上;
  • 清理包管理器缓存apt-get installrm -rf /var/lib/apt/lists/*,或 Alpine 用 apk --no-cache add
  • dev 依赖不进运行镜像pip install -r requirements-prod.txtnpm ci --omit=dev
  • 给服务加 HEALTHCHECK,让编排系统知道它活着。

五、小结

多阶段构建 + slim 基础镜像 + 正确的 COPY 顺序 + .dockerignore,四板斧下来镜像普遍能缩 80%~95%,换来的是:拉取更快、启动更快、攻击面更小。动手改改你的第一个 Dockerfile,从 FROM ... AS builder 开始。

参考资料

发表评论

暂无评论,快来抢沙发吧!