Docker 多阶段构建入门:把 Python 应用镜像从 900MB 瘦身到 120MB
第一次给自己的 Python 小项目写 Dockerfile 时,我信心满满地执行了 docker build,看到镜像大小的那一刻却愣住了:一个只有几十行代码的 FastAPI 接口,镜像居然有 900MB。传到服务器要等半天,换个环境重新拉取又要半天。从那以后,我开始认真研究镜像瘦身,而多阶段构建是其中性价比最高的一招。
为什么镜像会越来越胖
新手常写的 Dockerfile 大概是这个样子:从 python:3.12 出发,装 gcc、装依赖、复制代码,一气呵成。问题就藏在这里:编译某些依赖需要 gcc、make 这些构建工具,它们本身就有几百 MB;pip 安装时留下的缓存、下载的 wheel 包也会被一并打进镜像层。更关键的是,Docker 镜像是分层叠加的,某一层删掉的文件其实还占着空间,镜像只会越长越胖。
多阶段构建的核心思路
多阶段构建的做法很直观:把“构建”和“运行”拆成两个阶段。第一个阶段叫 builder,里面可以随便装编译工具、随便折腾;第二个阶段是最终的运行环境,只从 builder 里把真正需要的成果复制过来,其余一概不要。最终镜像里没有 gcc、没有 pip 缓存,只有一份干净的运行环境。
动手写一个两阶段 Dockerfile
假设项目结构很简单:一个主程序文件和一份依赖清单。
# 第一阶段:构建
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 第二阶段:运行
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /install /usr/local
COPY main.py .
CMD ["python", "main.py"]
关键点有三处:一是基础镜像选用 slim 而不是完整版,底子就轻了几百 MB;二是 pip 加上 --no-cache-dir,不留安装缓存;三是 COPY --from=builder 只搬运装好的依赖,不搬运构建工具。构建完成后用 docker images 对比一下:单阶段写法约 900MB,两阶段写法一般能压到 120MB 左右。
三个新手最容易踩的坑
第一,忘记写 .dockerignore。把 .git、pycache、.venv、测试数据都排除在构建上下文之外,否则 COPY 命令会把本地杂物全带进镜像。第二,COPY 的顺序不对。先复制依赖清单再安装依赖,这样只有依赖变化时才会重新安装;如果先复制全部代码,改一行代码就要重新装一遍依赖,构建时间会翻倍。第三,运行时缺系统库。有的 Python 包依赖 libpq、libxml2 这类系统库,构建阶段里有、运行镜像里却没有,容器一启动就报错,记得在运行阶段单独安装所需的运行时库。
小结
多阶段构建并没有什么魔法,它只是把“做饭的厨房”和“端上桌的菜”分开:厨房可以乱,端上桌的必须干净。对初学者来说,记住一个模板就够了:builder 阶段装依赖,运行阶段只拿结果,再配上 slim 基础镜像和 .dockerignore,镜像体积立刻就能降一个数量级。下次写 Dockerfile,不妨先问自己一句:这一层进镜像的东西,运行时真的需要吗?
- 点赞
- 收藏
- 关注作者
评论(0)