Docker 镜像瘦身实战:从 1.2G 到 180M
# Docker 镜像瘦身实战:从 1.2G 到 180M
## 前言
第一次把项目打包成 Docker 镜像,看到 `docker images` 里那个 1.2G 的数字时,我以为自己看错了——一个只有几千行代码的 Python 服务,怎么会有 1.2G?
推送到服务器时这个问题被放大:内网上传还好,一旦走公网,每次发布都要等几分钟,CI 流水线的绝大部分时间都耗在传输上。更麻烦的是容器启动也慢,回滚时尤其明显。
后来我把镜像压到了 180M,整个构建推送流程从几分钟降到十几秒。本文记录具体是怎么做的,以及哪些操作有效、哪些是无效努力。
## 一、先看镜像是怎么胖起来的
用一条命令就能看清:
```bash
docker history your-image:tag
```
它会列出每一层镜像的体积。我当时的输出大致是这样:基础镜像 `python:3.11` 占了 900 多 M,装依赖又加了 200 多 M,最后是代码本身几十 M。
问题就在这里:**我用的是完整版 Python 镜像**,里面带了完整的编译工具链、文档、包管理器的缓存,而这些东西运行时一个都用不到。
## 二、换基础镜像(效果最明显)
最立竿见影的一步,把:
```dockerfile
FROM python:3.11
```
换成:
```dockerfile
FROM python:3.11-slim
```
`slim` 版本删掉了编译工具、文档和多语言支持,只保留运行 Python 必需的内容。我的镜像从 1.2G 直接降到 400M 左右,**一行改动省了 800M**。
如果对体积还要更极致,可以用 `alpine` 版本,但要注意:`alpine` 用的是 musl libc,某些带 C 扩展的包(比如 `psycopg2`、`lxml`)可能装不上或需要额外编译,处理起来比重写 Dockerfile 还麻烦。我的建议是**先上 slim,够用就停**。
## 三、清理包管理器缓存(别让缓存白占空间)
在装依赖的同一层里清理,这一点很关键:
```dockerfile
RUN pip install --no-cache-dir -r requirements.txt
```
`--no-cache-dir` 让 pip 不保存下载缓存。如果用的是 apt:
```dockerfile
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
&& rm -rf /var/lib/apt/lists/*
```
注意 `rm -rf /var/lib/apt/lists/*` 必须和 `apt-get install` 写在**同一个 RUN 指令**里。Docker 的镜像是分层的,如果分成两行,第一层已经写入的缓存文件在第二层删除时只是被标记为删除,实际体积不会减少。
`--no-install-recommends` 也很重要,它会跳过"推荐但非必需"的依赖包,通常能少装一堆用不到的东西。
## 四、用 .dockerignore 挡住垃圾文件
很多人忽略了这一步。没有 `.dockerignore` 时,`COPY . /app` 会把整个项目目录都塞进镜像:`.git` 目录(可能几百 M)、本地虚拟环境、`__pycache__`、日志文件、测试用的大文件……
一个最小可用的配置:
```
.git
.gitignore
__pycache__
*.pyc
.venv
venv
.env
node_modules
*.log
.pytest_cache
```
我的项目里 `.git` 就占了 70 多 M,加上本地虚拟环境,这一刀下去又省了 100 多 M。
## 五、多阶段构建(编译型项目效果显著)
如果你的项目需要编译(Go、前端构建、Python 的 C 扩展),多阶段构建是标准做法:
```dockerfile
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir /wheels/*
COPY . .
CMD ["python", "main.py"]
```
第一个阶段负责编译,产出 wheels;第二个阶段只取编译结果,编译器、中间产物全部丢弃。对纯 Python 项目收益有限,对 Go 项目效果惊人——Go 编译出来的二进制配上 `scratch` 基础镜像,最终镜像可以只有十几 M。
## 六、一个容易踩的坑
我一开始就试过在 Dockerfile 最后加一条 `RUN rm -rf /root/.cache` 来清理,结果镜像体积一点没变。原因前面提过:删除发生在新的层,旧层里的数据还在镜像里躺着。
**记住这条原则:在同一个 RUN 里创建,就在同一个 RUN 里删除。**
至于"合并 RUN 减少层数",现在的 Docker 对层数已经优化得很好,不必为了合并而合并——可读性更重要。
## 小结
按我的收益排序:
1. 换 `slim` 基础镜像 —— 一行,省 60%+
2. 加 `.dockerignore` —— 挡掉 .git、虚拟环境
3. `--no-cache-dir` 和清理 apt 缓存(同层删除)
4. 多阶段构建 —— 编译型项目必做
镜像体积最终从 1.2G 降到 180M,推送时间从几分钟到十几秒。这些改动没有任何一个是"高级技巧",但组合起来的收益很实在,而且全程不到半小时。
- 点赞
- 收藏
- 关注作者
评论(0)