把镜像从 1.2G 压到 180M:一次 Docker 多阶段构建改造

举报
yd_293030668 发表于 2026/11/01 21:42:46 2026/11/01
【摘要】 把镜像从 1.2G 压到 180M:一次 Docker 多阶段构建改造接手一个 Python 服务的部署优化,docker images 一列出来吓一跳:单个镜像 1.2GB,一个 4G 内存的测试机器上光是拉镜像就要三分钟。拆开 Dockerfile 看,问题老生常谈——编译依赖和运行时环境全塞在一个镜像里,pip 缓存、编译工具链、临时文件样样没清。花了一个下午改造成多阶段构建,最终压...

把镜像从 1.2G 压到 180M:一次 Docker 多阶段构建改造

接手一个 Python 服务的部署优化,docker images 一列出来吓一跳:单个镜像 1.2GB,一个 4G 内存的测试机器上光是拉镜像就要三分钟。拆开 Dockerfile 看,问题老生常谈——编译依赖和运行时环境全塞在一个镜像里,pip 缓存、编译工具链、临时文件样样没清。花了一个下午改造成多阶段构建,最终压到 180MB,记录下过程。

改造前的 Dockerfile

FROM python:3.11

WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .

RUN apt-get update && apt-get install -y gcc libpq-dev
RUN python setup.py build_ext --inplace

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

几个明显的问题:

  1. 基础镜像 python:3.11 自带一堆用不上的工具,换成 -slim 能省近 300MB
  2. gcc、libpq-dev 这些只在编译期需要,装进最终镜像纯属浪费
  3. pip 缓存没清,/root/.cache/pip 动辄上百 MB
  4. COPY . . 把 .git、tests、本地调试文件全带了进去

第一步:换基础镜像

python:3.11 完整版约 1GB,python:3.11-slim 只有 120MB 左右。功能上唯一的差别是完整版预装了编译工具和常用库,而这些在多数 Web 服务里并不会用到。直接换:

FROM python:3.11-slim

这一步就砍掉了一大块。但换来一个坑:某些依赖需要编译,slim 镜像里没有 gcc,pip install 会失败——这正是多阶段构建要解决的问题。

第二步:多阶段构建

核心思路是把"编译"和"运行"拆开:编译阶段用完整环境把 wheel 造好,运行阶段只拷贝成品。

# ---- 构建阶段 ----
FROM python:3.11 AS builder

WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc g++ libpq-dev \
    && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
# 编译成 wheel,不装到系统里
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

# ---- 运行阶段 ----
FROM python:3.11-slim

WORKDIR /app

# 只拷 wheel,装完删掉,不留编译工具
COPY --from=builder /wheels /wheels
COPY requirements.txt .
RUN pip install --no-cache-dir --no-index --find-links=/wheels -r requirements.txt \
    && rm -rf /wheels \
    && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false

COPY app/ ./app/

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

关键点:

  • pip wheel 只编译不安装,产物是 .whl 文件
  • 运行阶段用 --no-index --find-links=/wheels 从本地 wheel 装,不重新下载也不重新编译
  • --no-cache-dir 避免留下 pip 缓存
  • COPY app/ ./app/ 只复制源码目录,.git 和测试文件在 .dockerignore 里排除

第三步:.dockerignore

这一步常被忽略,但影响很大:

.git
.gitignore
tests/
*.md
__pycache__/
*.pyc
.pytest_cache/
.env
node_modules/

改造前镜像里塞了近 200MB 的 .git 历史和测试数据,COPY . . 一句全带进去了。

踩到的坑

坑一:动态链接库没跟着 wheel 走。 运行阶段装完 wheel 后服务启动报 ImportError: libpq.so.5: cannot open shared object file。psycopg2 编译出来的 wheel 依赖 libpq,但 slim 运行环境里没有这个库。解决方式是运行阶段保留运行时库(不要 libpq-dev 这个开发包,只要 libpq5):

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

判断一个库是"编译期"还是"运行期",我的笨办法是编译完先不装,跑起来看报什么错再补。比起一次性把 dev 包全留着,最终也就多几十 KB。

坑二:多阶段构建里的时区。 服务里用了本地时间格式化日志,容器默认 UTC,日志时间整体差 8 小时。slim 镜像里没有 tzdata,TZ=Asia/Shanghai 环境变量也不生效。补上:

RUN apt-get update && apt-get install -y --no-install-recommends tzdata \
    && ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
    && echo $TZ > /etc/timezone

坑三:构建缓存顺序。 COPY requirements.txt 要放在 COPY app/ 之前,否则每次改一行代码都要重新装一遍依赖,构建时间从 20 秒暴涨到 3 分钟。这个道理都懂,但改造时很容易因为"先把代码拷进去更顺眼"而踩回去。

结果

指标 改造前 改造后
镜像大小 1.2 GB 180 MB
冷启动拉取耗时 ~180s ~25s
构建耗时(无缓存) 4min 3min
构建耗时(改代码后) 3min 22s

镜像变小最直接的好处是部署快,尤其是在多节点同时拉镜像的场景下。其次是安全面缩小——编译工具链、apt 源、pip 缓存这些东西不在运行时镜像里,潜在的攻击面也少了。

小结

多阶段构建不是什么新技巧,但要压到接近理论下限,还是要一个包一个包地看:哪些是编译期需要、哪些是运行期需要、哪些是压根不需要。我的经验是留一个 --no-install-recommends 的习惯,把所有非必需的依赖都挡在门外,再配合定期用 dive 工具看一下镜像分层,基本上能把水分挤干净。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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