多阶段构建是用逻辑隔离替代环境混杂,通过builder和runner两阶段分离编译与运行:builder装工具链并清理缓存,runner仅复制最小依赖到slim或distroless镜像,禁用pip缓存、避免alpine musl陷阱,最终实现镜像瘦身与安全加固。
多阶段构建不是“多写几个 from”,而是用逻辑隔离代替环境混杂——把编译时需要的工具链、临时文件、缓存、调试符号全部挡在最终镜像之外,只留下运行必需的二进制、字节码和业务代码。
明确划分 builder 和 runner 两个阶段
builder 阶段专用于编译:装 gcc、libffi-dev、openssl-dev 等系统依赖,运行 pip install 编译 C 扩展(如 psycopg2、numpy),生成 .so 或 .pyc;runner 阶段只负责运行:基础镜像选 python:3.11-slim(约 110MB,glibc 兼容性好),不装任何编译工具,也不执行 pip install。
- builder 阶段结尾务必清理:apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/pip-build-*
- runner 阶段禁止出现 RUN pip install,否则 wheel 缓存、__pycache__、.dist-info 全部固化进镜像层
- 用 AS 给 builder 命名(如 FROM python:3.11-slim AS builder),方便后续 COPY --from=builder 引用
精准复制依赖,拒绝整目录搬运
别 COPY 整个虚拟环境或 site-packages 目录——里面混着测试模块、文档、源码压缩包、pip 解压残留等无用内容。要的是“运行时最小集合”,不是“构建期快照”。
- builder 中用 pip install --no-cache-dir --target /app/deps -r requirements.txt,把依赖装到干净独立目录
- runner 中只 COPY --from=builder /app/deps /usr/local/lib/python3.11/site-packages/
- 若用 --user 安装,则 COPY --from=builder /root/.local /root/.local,并设置 PYTHONPATH=/root/.local/lib/python3.11/site-packages
慎选基础镜像,避开 Alpine 的 musl 陷阱
Alpine 镜像虽小(~5MB),但 musl libc 导致绝大多数 Python wheel 不兼容,被迫源码编译,反而增大体积、延长构建时间、引入不确定性。
- 生产环境优先用 python:3.11-slim:体积适中、wheel 复用率高、glibc 兼容稳定
- 确需 Alpine 时,改用 apk add py3-numpy 等预编译包,并用 && apk del .build-deps 清理构建依赖
- 绝对禁用 pip cache:--no-cache-dir 是硬要求,否则 /root/.cache/pip 会悄悄吃掉几十 MB
终极瘦身:向 scratch 或 distroless 迈进
当项目纯 Python 且无 C 扩展、或所有扩展都已静态链接,可考虑 runner 阶段用 FROM scratch 或 distroless/python:镜像只剩业务代码 + 已编译依赖 + CA 证书 + 时区数据,体积压至 12–25MB。
- scratch 镜像无 shell、无包管理器、无调试工具,必须确保所有动态链接库已静态打包或显式 COPY
- distroless 镜像提供最小运行时支撑(如 ca-certificates、tzdata),比 scratch 更稳妥,适合多数生产场景
- 切换前务必验证:python -c "import numpy, requests" 无报错,且应用 HTTP 健康检查能通










