# Docker 镜像瘦身实战:从 1.2G 到 80MB 的五个优化步骤

一个 50 行的 Flask 应用打出 1.2G 的 Docker 镜像------这不是段子,是我第一次写 Dockerfile 的真实经历。

引言

第一次把项目容器化的时候,我写出了这样的 Dockerfile:

dockerfile 复制代码
FROM python:3.12
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

docker build 跑了 5 分钟,出来一个 1.2G 的镜像。每次 CI/CD 流水线卡在 docker push 那一步,部署一次要等 8 分钟。后来花了两个月系统性地优化,最终把镜像从 1.2G 压到了 80MB,构建时间从 8 分钟降到 40 秒。

这篇文章总结了五个关键优化步骤,每一步都附带了可复用的 Dockerfile 片段和背后的原理说明。


1. 多阶段构建:分离编译环境与运行环境

多阶段构建(Multi-stage Build)是 Docker 17.05 引入的特性,也是镜像瘦身最重要的手段。核心思想很简单:用一个阶段做构建,用另一个阶段做运行,两个阶段共享产物但不共享环境。

1.1 为什么需要多阶段?

传统的单阶段 Dockerfile 有一个根本矛盾:构建时需要编译器、头文件、开发工具链,但运行时一个都不需要。单阶段构建无法摆脱这些构建依赖------你的最终镜像里带着 gcc、git、make 等几百 MB 永远用不上的东西。

1.2 多阶段 Dockerfile 模板

dockerfile 复制代码
# === 阶段 1:builder(构建环境) ===
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user -r requirements.txt

# === 阶段 2:runner(运行环境) ===
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]

关键指令:

  • FROM ... AS builder:给第一阶段命名,后续可以引用。
  • COPY --from=builder :从 builder 阶段拷贝文件到当前阶段,只拷贝你指定的路径。builder 阶段装的 gcc、git 等全部留在第一阶段,不会进入最终镜像。
  • pip install --user :把 Python 包装到 /root/.local,这是一个独立目录,方便后续精确拷贝。

1.3 多阶段的效果

阶段 镜像大小 包含内容
builder(python:3.12 ~1GB Python + gcc + git + make + ...
runner(python:3.12-slim ~150MB + 你的代码 Python 运行时 + 你的依赖

builder 阶段在构建完成后被丢弃,只保留 runner 阶段的镜像。你可以用 docker image ls 看到 builder 的中间镜像,用 docker image prune 清理。


2. 选择正确的基础镜像

基础镜像的选择直接决定了镜像的"地板"有多高。Python 官方提供了多个 tag,它们的大小差异巨大:

镜像 Tag 压缩后大小 适用场景
python:3.12 ~1GB ❌ 不要用于生产
python:3.12-slim ~150MB ✅ 生产环境首选
python:3.12-alpine ~50MB ⚡ 极致瘦身

2.1 slim vs alpine 的选择

slim基于 Debian,使用 glibc。砍掉了编译工具链、文档、man pages,但保留了运行时必需的库。兼容性最好------几乎所有 Python 包都有兼容 glibc 的预编译 wheel。

alpine基于 Alpine Linux,使用 musl libc。比 slim 再小 100MB,但代价是某些依赖 C 扩展的 Python 包(如 pandas、lxml、grpcio)需要从源码编译或安装额外的系统包:

dockerfile 复制代码
FROM python:3.12-alpine
RUN apk add --no-cache gcc musl-dev libffi-dev  # pandas 等包需要的系统依赖

建议:默认用 slim,只有在对镜像大小有极致要求时再迁移到 alpine。

2.2 锁版本,不要用 latest

dockerfile 复制代码
FROM python:3.12-slim  # ✅ 明确版本
FROM python:slim        # ❌ latest tag,今天和明天可能不一样

3. 层缓存优化:Dockerfile 的书写顺序决定构建速度

Docker 的构建是分层进行的------每个 RUNCOPYADD 指令产生一个新层。如果某一层的输入没有变化,Docker 会复用缓存,跳过该层的执行。

核心原则:把变化频率最低的文件放在前面,变化频率最高的放在最后。

3.1 错误 vs 正确的 COPY 顺序

dockerfile 复制代码
# ❌ 错误:代码变了,依赖要重新安装
COPY . /app
RUN pip install -r requirements.txt
dockerfile 复制代码
# ✅ 正确:依赖文件单独 COPY,充分利用缓存
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY . /app

原理分析:

requirements.txt 的修改频率远低于源代码。当你改了一行业务逻辑,requirements.txt 的 hash 不变 → pip install 层命中缓存 → 只有最后一个 COPY . /app 层被重建。

构建时间对比(一个典型的 Python 项目):

场景 构建时间
依赖 COPY 在后(每次重装) ~3 分钟
依赖 COPY 在前(缓存命中) ~2 秒

3.2 .dockerignore:别让垃圾文件破坏缓存

COPY . /app 会把当前目录的所有文件复制进镜像------包括 .git__pycache__.venvnode_modules 等。这些文件不仅增大镜像体积,还会导致不必要的缓存失效。

dockerignore 复制代码
# .dockerignore
__pycache__
*.pyc
*.pyo
.env
.git
.gitignore
.venv
venv
node_modules
*.log
.DS_Store
docker-compose.yml
README.md

一个包含 .git 目录的项目,.dockerignore 能减少 100MB+ 的 context 传输。


4. pip install 的优化参数

pip install 默认行为会在镜像中留下大量运行时不需要的产物。三个参数可以直接砍掉:

dockerfile 复制代码
RUN pip install \
    --no-cache-dir \      # 不保存 pip 下载缓存
    --no-compile \         # 不生成 .pyc 字节码文件
    -r requirements.txt

各参数说明:

  • --no-cache-dir :pip 默认会把下载的 .whl 文件缓存到 ~/.cache/pip,这在容器里完全没用------容器是无状态的,下次构建不会复用这个缓存。去掉后能省 100MB+。
  • --no-compile :Python 默认会在安装包时编译 .pyc 文件。在容器里这些字节码文件几乎不会被用到(因为容器启动后通常只执行一次入口脚本),去掉后能省几十 MB。

4.1 依赖拆分:生产环境不需要 pytest

dockerfile 复制代码
# requirements.txt ------ 只放运行时依赖
flask==3.0
gunicorn==22.0

# requirements-dev.txt ------ 开发/测试工具
pytest>=8.0
pytest-cov>=5.0
black>=24.0

Dockerfile 里只装生产依赖:

dockerfile 复制代码
RUN pip install --no-cache-dir -r requirements.txt

这比"全装进去然后不需要的放着"直接省掉几十个包。


5. Distroless 镜像:安全与瘦身的终极选择

Google 开源的 distroless 镜像只包含你的应用及其运行时依赖------连 shell 都没有。

dockerfile 复制代码
# builder 阶段
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# runner 阶段:distroless
FROM gcr.io/distroless/python3
COPY --from=builder /root/.local /root/.local
COPY . /app
WORKDIR /app
ENV PATH=/root/.local/bin:$PATH
CMD ["app.py"]

Distroless 镜像的特点:

  • 没有包管理器(aptapkpip 均不可用)
  • 没有 shell(/bin/sh 不存在)
  • 没有 curl、wget 等工具
  • 只包含 Python 解释器和标准库

安全收益: 即使攻击者找到了一个 RCE 漏洞进入了容器,他也无法执行任何命令------因为没有 shell 可以执行。这符合最小攻击面原则。

调试限制: distroless 镜像不能用 docker exec -it <container> /bin/sh 进入容器。调试时使用 slim 镜像,确认问题后切换到 distroless。

镜像大小对比(以同一个 Flask 应用为例):

基础镜像 最终镜像大小
python:3.12 1.2GB
python:3.12-slim 180MB
python:3.12-slim + 多阶段 120MB
python:3.12-alpine + 多阶段 80MB
distroless + 多阶段 50MB

完整瘦身 Dockerfile

把以上五个优化整合到一起:

dockerfile 复制代码
# ===== builder stage =====
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir --no-compile -r requirements.txt

# ===== runner stage =====
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
EXPOSE 5000
USER 1000:1000
CMD ["python", "app.py"]

关键设计决策:

  • USER 1000:1000:以非 root 用户运行,最小化安全风险。
  • EXPOSE 5000:声明端口,配合 docker-compose 或 K8s 做服务发现。
  • ENV PATH :确保 Python 能找到装在 /root/.local 里的包。

最终效果:镜像从 1.2G → 80MB,瘦了 93%。构建时间从 8 分钟降到 40 秒。


总结

优化步骤 核心操作 节省的体积
多阶段构建 FROM ... AS builder + COPY --from=builder ~500MB(排除编译工具)
换基础镜像 python:3.12python:3.12-slim ~850MB
层缓存优化 COPY requirements.txt 前置 增量构建从 3 分钟 → 2 秒
pip 优化 --no-cache-dir + 依赖拆分 ~100MB+
distroless gcr.io/distroless/python3 进一步 30~50MB

镜像瘦身不是追求数字好看------更小的镜像意味着更快的构建、更快的拉取、更快的扩缩容。在微服务架构中,几十个服务每个都小 90%,累计的时间节省是巨大的。


参考链接:


如果这篇文章帮你优化了镜像大小,欢迎点赞、收藏、关注。有问题可以在评论区交流。

相关推荐
程序员老赵18 小时前
10 分钟部署 OpenCode:Docker 一键安装,浏览器打开就能用 AI 写代码(附完整命令与排错)
docker·容器·ai编程
WangMingHua1111 天前
LM Studio Docker 部署——本地大模型一键启动
docker
曲幽2 天前
别再用网页翻译看源码了!你的私人翻译神器LibreTranslate,部署避坑指南来了
python·docker·web·pot·translate·libretranslate·arogstranslate
武子康4 天前
调查研究-183 Apple container:Mac 上用轻量 VM 跑 Linux 容器,Swift 会改写本地容器体验吗?
docker·容器·apple
宋均浩5 天前
# GitHub Actions 实战:从零搭建 CI/CD 流水线的 5 个核心配置
ci/cd
Alsn867 天前
等待学习-学习目录:Docker 容器安全攻防
学习·安全·docker
程序员老赵7 天前
服务器没有桌面?Docker 跑个 Chrome,浏览器就能远程用
docker·容器·devops
杨浦老苏7 天前
轻量级Docker仪表板Servedash
运维·docker·监控·群晖·仪表板
正经教主7 天前
【docker基础】 第八周:容器监控与应用更新策略
运维·docker·容器