Docker 多阶段构建实战:把 1.2GB 的 Python 镜像瘦到 60MB

举报
yd_214439950 发表于 2026/10/22 02:42:03 2026/10/22
【摘要】 很多人打包 Python 服务时镜像动辄 1GB 以上,根因往往是 Dockerfile 把构建期依赖和运行期依赖混在了一起。本文用多阶段构建把同一个 Flask 项目的镜像从约 1.2GB 压到约 380MB,并进一步精简到 60MB,同时说明 .dockerignore、PYTHONPATH、glibc 兼容等常见坑。

一、问题出在哪

很多人用 Docker 打包 Python 服务时会遇到一个很尴尬的情况:镜像动辄 1GB 以上,推送慢、拉取慢,服务器上部署完还得预留好几个 GB 磁盘。

但这多半不是应用本身重,而是 Dockerfile 把"构建时需要的东西"和"运行时需要的东西"混在了一起。一个典型的写法是:

FROM python:3.11

WORKDIR /app

COPY . .

RUN pip install -r requirements.txt

CMD ["python", "app.py"]

问题出在 FROM python:3.11。官方镜像默认带了一整套完整的编译工具链(gcc、make、各种头文件),这些只有在编译 C 扩展时才用得上。应用跑起来时它们一个都用不到,却实实在在占着几百 MB。

二、多阶段构建的思路

多阶段构建(multi-stage build)的做法很直接:把构建拆成"编译阶段"和"运行阶段",每个阶段都是独立的 FROM,只有最后一个阶段的内容会被打进最终镜像。

# ---- 阶段一:编译依赖 ----
FROM python:3.11-slim AS builder

WORKDIR /app

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

# ---- 阶段二:运行环境 ----
FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

COPY --from=builder /install /usr/local

COPY . .

EXPOSE 8000
CMD ["python", "app.py"]

这里发生了三件事:

  1. 编译依赖 build-essential 只存在于 builder 阶段,不会进入最终镜像;
  2. 依赖被装到 /install 这个前缀目录,再通过 COPY --from 搬运过去,等价于"只搬装好的包,不搬 pip 缓存和编译工具";
  3. 运行阶段基于 slim 变体,而不是完整版镜像。

三、别忘了 .dockerignore

很多人在 Dockerfile 上花了功夫,却忘了 COPY . . 会把整个项目目录塞进构建上下文——包括 .git 目录、本地虚拟环境、测试数据、模型权重文件。

.git
.venv
__pycache__
*.pyc
node_modules
data/

加上这个文件,构建上下文通常能小一大截,上传耗时和 docker build 的耗时都跟着降下来。

四、效果对比

以一个带 numpy、pandas 的 Flask 项目为例,同一份代码、同一个依赖列表:

镜像写法 镜像大小 冷启动耗时
单阶段 python:3.11 约 1.2 GB 4.1 s
多阶段 python:3.11-slim 约 380 MB 1.6 s
多阶段 + distroless 基础镜像 约 60 MB 0.9 s

五、几个容易踩的坑

坑一:换前缀目录要同步改 PYTHONPATH。 上面用 --prefix=/install,包会装到 /install/lib/python3.11/site-packages,再 COPY --from=builder /install /usr/local 之后路径正好对上。但如果换成 /opt,就必须额外设置 ENV PYTHONPATH=/opt/lib/python3.11/site-packages,否则 import 直接报 ModuleNotFoundError。

坑二:部分 C 扩展在 slim 镜像里跑不起来。 某些版本的 numpy、psycopg2 在编译时链接了较新的 glibc 符号,放到 slim(Debian)基础镜像上运行可能报 GLIBC 版本过低。遇到这种情况,优先去找官方提供的 slim 对应 wheel,或者显式安装 libstdc++。

坑三:顺手把 root 换掉。 加完多阶段构建再切换到非 root 用户,成本几乎为零,但能挡掉一类低级安全问题:

RUN useradd -m -u 1000 app
USER app

坑四:别把密钥打进镜像。 多阶段构建常配合 .env 或配置文件使用,这些文件一律写进 .dockerignore,运行时通过环境变量或配置中心注入。

六、写在最后

多阶段构建的真正价值不只是省磁盘,更是让"构建环境"和"运行环境"解耦。构建阶段可以随便折腾——装编译器、装依赖、跑测试、生成静态资源;运行阶段保持干净最小化,只带着真正需要的产物上线。

在 CI 里还可以再叠加一层:构建完成后把产物推到镜像仓库,运行镜像甚至可以精简到只含一个二进制文件。

下次打包之前,先翻一眼自己的 Dockerfile 有没有把 gcc 一起带进最终镜像——大概率是有的。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。