Python 3.8.1 Docker 镜像瘦身:从 1.2G 砍到 60MB

做 Python 开发的,4 步把 Python 3.8.1 镜像干到 60MB,原理、代码、避坑点。

一、先搞懂:镜像为啥会 “虚胖”?

别上来就瞎优化,先搞清楚体积都花在哪了

  1. 基础镜像选 “全量包”:默认python:3.8.1是 Debian 全量系统,带了apt全套工具、一堆用不上的库,光基底就 900MB—— 相当于你出门带整个工具箱,没必要;

  2. 构建依赖没清干净:装个numpygccpython3-dev,编译完这些工具还留着;pip默认缓存安装包,几百 MB 就这么占着,纯属 “吃完外卖不扔盒子”;

  3. 依赖混了 “开发垃圾”requirements.txt里塞了pytest(测试)、flake8(代码检查),线上运行根本用不上,纯占空间;

  4. COPY 命令 “一锅端”:直接COPY . .,把.gitvenvtests/全塞进去 —— 这些本地开发文件,容器里看都不会看一眼。

二、实战步骤:4 步砍体积

第一步:选对基础镜像 —— 别头铁上 alpine!

基础镜像是 “地基”,选不对后续再优化也白搭。Python 3.8.1 就 3 种常用镜像,咱程序员按场景选,别瞎跟风:

镜像名 系统基底 体积 适用场景 坑点提醒
python:3.8.1 Debian 全量 ~900MB 几乎不用(除非你要 Debian 全套工具) 臃肿到离谱,推包能急死你
python:3.8.1-slim Debian slim ~180MB 90% 的 Python 项目(优先选!) 兼容性好,不用跟 libc 死磕
python:3.8.1-alpine Alpine ~40MB 纯 Python 项目(无 C 扩展依赖) 用 musl libc,部分包不兼容

先从slim开始!别一上来就冲alpine—— 上次我用alpinepsycopg2,编译报错查了 2 小时,最后发现是 musl libc 不兼容,白折腾。

第二步:多阶段构建 ——“编译和运行分开干”

这是瘦身核心!原理特简单:第一阶段负责 “编译依赖、准备代码”,第二阶段只拿 “能跑起来的文件”,构建工具、缓存全丢掉。

直接上可复用的 Dockerfile(我项目里扒出来的,改改路径就能用):

# 第一阶段:构建阶段(只干活,不做最终镜像)

FROM python:3.8.1-slim AS builder

WORKDIR /app  # 固定工作目录,避免文件散架

# 1. 装系统级构建依赖

RUN apt-get update && \\

  apt-get install -y --no-install-recommends \\

   gcc \          # 编译C扩展(如numpy)必须要

  python3-dev \  # Python开发文件,编译依赖用

  libssl-dev && \\# 示例:如果依赖需要SSL(如requests)

  rm -rf /var/lib/apt/lists/\*  # 清apt缓存!不然多100MB+

# 2. 先复制requirements.txt(利用Docker缓存!)

# 重点:改代码不会触发依赖重装,只有改requirements才会,省时间

COPY requirements.txt .

# 3. 装Python依赖(--no-cache-dir必加!禁pip缓存,省几百MB)

RUN pip install --no-cache-dir -r requirements.txt

# 4. 复制项目代码(只复制运行必需的!比如app/,别COPY . .)

COPY app/ /app/app/

# 第二阶段:运行阶段(最终镜像,只留能跑的)

FROM python:3.8.1-slim  # 跟构建阶段基底一致,避免兼容问题

WORKDIR /app

# 从构建阶段“偷”东西:只拿依赖和代码,构建工具全丢

COPY --from=builder /usr/local/lib/python3.8/site-packages/ /usr/local/lib/python3.8/site-packages/

COPY --from=builder /app/app/ /app/app/

# 清冗余(可选,但建议加)

RUN apt-get autoremove -y && apt-get clean

# 启动命令(改你自己的入口文件,比如main.py)

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

为啥要拆两阶段?比如第一阶段的gcc,编译完numpy就没用了,带到线上镜像里纯属占地方 —— 这步能直接砍掉 50% 以上体积。

第三步:细节优化

多阶段是核心,但细节没做好,镜像还是会胖,这几个点必须卡严:

1. 系统依赖:“用啥装啥,用完就清”
  • 装包加--no-install-recommends:Debian 默认装 “推荐依赖”,比如装gcc会带一堆没用的库,加这个参数能少装 80% 冗余;

  • 构建阶段必清apt缓存:rm -rf /var/lib/apt/lists/*—— 这个目录是apt update下载的包索引,留着没用,清完能省 100MB+;

  • 运行阶段不装构建工具:gccpython3-dev只在第一阶段出现,第二阶段提都别提。

2. Python 依赖:“只留生产用的”
  • requirements.txt别混开发依赖:把pytestflake8ipython这些开发工具移到requirements-dev.txt,生产环境只装运行依赖;

  • 生成 requirements 用这个命令:pip freeze --exclude-editable > requirements.txt—— 排除本地 editable 依赖(比如-e .),避免镜像里装不上;

  • 优先用预编译包:比如装psycopg2别直接装,用psycopg2-binary(预编译好的),不用装gcc,还快。

3. 文件复制:用.dockerignore“过滤垃圾”

别瞎用COPY . .!创建.dockerignore,把本地垃圾全挡在外面,这是我项目里在用的配置,直接抄:

# 版本控制垃圾

.git

.gitignore
# Python缓存(本地生成的,容器里没用)

__pycache__

*.pyc

*.pyo

*.pyd

# 开发相关(线上用不上)

venv/          # 本地虚拟环境,镜像里有Python,不用带

tests/         # 测试代码,线上不跑测试

docs/          # 文档,容器里看不着

logs/          # 本地日志,容器里会重新生成

.idea/         # PyCharm配置

.vscode/       # VS Code配置

# 其他冗余

*.md           # 说明文档,线上用不上

requirements-dev.txt  # 开发依赖,别塞进去

第四步:进阶:Alpine 镜像再砍到 60MB(慎选!)

如果你的项目是纯 Python(无 C 扩展依赖,比如 Flask、Django 纯接口),可以试试alpine,体积能从 200MB 砍到 60MB,但坑点要注意:

Alpine 版 Dockerfile 示例:
# 构建阶段

FROM python:3.8.1-alpine AS builder

WORKDIR /app

# Alpine用apk装包,--no-cache自动清缓存,不用手动删

RUN apk add --no-cache \\

   gcc \\

 musl-dev \  # 替代Debian的libc6-dev,Alpine用musl

  openssl-dev

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app/ /app/app/

# 运行阶段

FROM python:3.8.1-alpine

WORKDIR /app

COPY --from=builder /usr/local/lib/python3.8/site-packages/ /usr/local/lib/python3.8/site-packages/

COPY --from=builder /app/app/ /app/app/

CMD ["python", "app/main.py"]
Alpine 避坑点:
  • 依赖兼容问题:如果装包报错 “ImportError: No module named _ctypes”,大概率是 musl libc 不兼容,解决方案:
  1. 换预编译包(如psycopg2-binary替代psycopg2);

  2. 装缺失的依赖(比如_ctypes需要libffi-dev,加apk add libffi-dev);

  3. 实在搞不定就退回到slim,别死磕 —— 开发效率比那几十 MB 重要。

三、避坑

  1. 多阶段路径不对应:上次我第一阶段把代码放/app/code/,第二阶段复制到/app/app/,启动时报 “找不到 main.py”,查了半小时才发现路径对不上 ——两个阶段的文件路径必须一模一样

  2. .dockerignore 没配全:漏了__pycache__/,结果镜像里带了一堆.pyc文件,用docker run --rm 镜像名 ls /app能查出来,发现多余文件就补.dockerignore

  3. Alpine 装包不看文档:直接用alpinenumpy,编译报错才去查文档,发现要先装gfortran——用 Alpine 前,先查依赖的官方文档,看有没有 Alpine 适配说明

四、优化效果:数据说话(

我用同一个 Flask 项目测的,体积差距一目了然:

优化方式 基础镜像 镜像体积 推仓库时间(100Mbps)
未优化(单阶段) python:3.8.1 ~1.2GB 10 分钟 +
基础优化(slim + 多阶段) python:3.8.1-slim ~200MB 1 分钟左右
极致优化(alpine + 多阶段) python:3.8.1-alpine ~60MB 20 秒以内

五、总结

镜像瘦身的本质,就是 线上镜像只留能跑起来的最小集合—— 基础镜像选精简的、构建依赖用完就丢、Python 依赖只留生产的、文件只复制必需的。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐