多阶段构建通过分离构建与运行环境实现瘦身:构建阶段用含编译器的镜像生成wheel并清理临时文件,运行阶段仅复制必要产物到精简镜像,避免gcc等工具链残留、缓存污染及源码泄露。

多阶段构建不是“加个AS就能瘦”,关键在构建阶段不污染运行层、产物拷贝要精准、缓存复用要合理。
为什么单阶段RUN pip install会把gcc留在镜像里
Python项目常依赖C扩展(如psycopg2、cryptography),pip install时需要gcc、musl-dev等编译工具。单阶段Dockerfile里写RUN apt-get install -y gcc && pip install -r requirements.txt,哪怕后续RUN apt-get remove -y gcc,gcc的二进制文件仍躺在前一层里——Docker分层机制决定了删除只是标记,不是物理擦除。
- 基础镜像如
python:3.11默认带完整工具链,体积超800MB;换成python:3.11-slim能砍掉一半,但依然含apt和基础编译环境 - 真正安全的做法是:构建阶段用带编译器的镜像,运行阶段只用
python:3.11-slim甚至python:3.11-slim-bookworm - 别信“装完删就没事”——必须让安装+清理在同一个
RUN里,且仅限构建阶段
怎样让wheel包真正复用、避免重复下载
构建阶段生成wheel包,是为了让运行阶段跳过编译、直接安装二进制包。但若路径或权限不对,pip install /wheels/*.whl会失败或退回到源码编译。
- 构建阶段务必用
pip wheel --no-cache-dir --no-deps --wheel-dir /app/wheels -r requirements.txt,其中--no-deps防止递归拉取未声明的依赖 - 确保
COPY --from=builder /app/wheels /wheels后,运行阶段RUN pip install --no-cache /wheels/*.whl前,/wheels目录存在且可读 - 如果
requirements.txt里有-e .或本地路径包,pip wheel无法处理,得单独COPY源码并pip install -e,此时要额外清理.git、tests/等无关内容
哪些东西绝对不能COPY进最终镜像
最终镜像里出现以下任意一项,都说明多阶段没切干净:
Docker Hub (hub.docker.com)。此技能适用于所有 Docker Hub 操作,包括读取、创建、更新和删除数据。只要任务涉及 Docker Hub,就使用此技能。
立即学习“Python免费学习笔记(深入)”;
-
/usr/lib/gcc、/usr/bin/gcc、/usr/bin/make—— 构建工具链残留 -
~/.cache/pip、/var/lib/apt/lists、/root/.npm—— 包管理器缓存 -
.git/、node_modules/、dist/、target/—— 构建上下文误传 - 源码里的
secrets.py、.env.example、CI配置文件 —— 敏感信息或非运行必需文件
验证方法:构建完执行docker run --rm <image> ls -la /usr/bin | grep -E "(gcc|make|apt)"</image>,结果应为空。
BuildKit启用后,几个关键优化点
启用DOCKER_BUILDKIT=1后,RUN --mount=type=cache能显著加速pip和apt缓存复用,但默认不生效,必须显式声明。
- 构建阶段加
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt,避免每次重下wheel - 用
RUN --mount=type=cache,target=/var/lib/apt/lists apt-get update && apt-get install -y ...,省掉apt-get clean步骤 - 禁用
--progress=plain时看不到mount日志,容易误判缓存是否命中;建议CI中保留auto模式便于排查 - 注意:cache mount只在同名构建阶段内有效,builder和final阶段之间不共享
最易被忽略的是.dockerignore——没它,整个项目目录(含venv/、.git/、大日志)都会打进构建上下文,哪怕你没COPY也白白增加传输开销。

















