Docker 镜像优化实战:多阶段构建、镜像瘦身与安全加固 原创
温馨提示:
本文最后更新于 2026-10-26,已超过 0 天没有更新。
若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。
Docker 镜像动辄几百 MB 甚至几个 GB,不仅拉取慢、存储贵,还扩大攻击面。镜像优化不是「删几个文件」那么简单,而是从构建方式、基础镜像选择到安全加固的系统工程。这篇把多阶段构建、镜像分层、安全扫描串成一条可落地的优化链路。
一、镜像为什么这么大:分层的代价
Docker 镜像由只读层组成,每一层对应 Dockerfile 的一条指令。问题出在:
- 层不会自动合并:10 个 RUN 命令 = 10 层
- 删除不缩小:上一层删了文件,下一层还在,总大小不变
- 缓存污染:构建时装的临时依赖永久留在镜像里
# 反例:典型的「垃圾」Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install flask gunicorn
RUN apt-get clean && rm -rf /var/lib/apt/lists/* # 看似清理,实际层还在
COPY . /app
WORKDIR /app
CMD ["gunicorn", "app:app"]
这个镜像最终约 800MB,其中 600MB 是 apt 缓存和 Python 编译工具链——运行时根本用不到。
二、多阶段构建:编译和运行分离
核心思想:用「构建阶段」装编译工具、跑编译,只把产物复制到「运行阶段」。
# 阶段 1:构建
FROM python:3.11 AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# 阶段 2:运行
FROM python:3.11-slim
WORKDIR /app
# 只复制安装好的包,不复制 pip 缓存和编译工具
COPY --from=builder /root/.local /root/.local
COPY . .
# 确保用户级包在 PATH
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
效果:从 800MB 降到 150MB。再换 python:3.11-alpine 可以到 60MB。
三、基础镜像选择:不是越小越好
| 镜像 | 大小 | 特点 | 适用 |
|---|---|---|---|
| ubuntu:22.04 | 78MB | 功能全,兼容好 | 需要大量系统工具 |
| debian:bookworm-slim | 74MB | 精简 Debian | 平衡选择 |
| alpine:3.18 | 7MB | musl libc,极小 | 纯静态二进制 |
| distroless | 20MB | 无 shell、无包管理器 | 最高安全,Go/Java 应用 |
| scratch | 0B | 完全空 | 静态编译的 Go 二进制 |
注意:Alpine 的 musl libc 和 glibc 不兼容,Python 的某些包(如 numpy、grpcio)在 Alpine 上需要源码编译,反而更慢。Go 应用用 scratch 或 distroless 最干净。
四、Layer 合并与缓存优化
# 反例:每层都留垃圾
RUN apt-get update
RUN apt-get install -y curl
RUN curl -O https://example.com/file.tar.gz
RUN tar -xzf file.tar.gz
RUN rm file.tar.gz
# 正例:合并 + 清理在同一层
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& curl -O https://example.com/file.tar.gz \
&& tar -xzf file.tar.gz \
&& rm file.tar.gz \
&& apt-get purge -y curl \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
构建缓存顺序:把不常变的放前面(依赖安装),常变的放后面(代码 COPY):
# 先装依赖(缓存友好)
COPY requirements.txt .
RUN pip install -r requirements.txt
# 后 COPY 代码(经常变,不破坏前面缓存)
COPY . .
五、安全加固:不只是「最小化」
# 1. 非 root 运行
RUN groupadd -r app && useradd -r -g app app
USER app
# 2. 只读根文件系统
docker run --read-only --tmpfs /tmp myapp
# 3. drop capabilities
docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp
# 4. 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
六、镜像扫描:上线前的最后一关
# 用 Trivy 扫描漏洞
trivy image myapp:latest
# 只看高危和严重
trivy image --severity HIGH,CRITICAL myapp:latest
# 生成 SBOM(软件物料清单)
trivy image --format cyclonedx myapp:latest > sbom.json
常见高危来源:旧版 openssl、未修复的 CVE、包含 secret 的层。扫描结果必须进 CI 门禁,高危不过线不准发版。
七、实战:Go 应用的极致优化
# 阶段 1:构建
FROM golang:1.22-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# 阶段 2:运行
FROM scratch
COPY --from=builder /build/app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/app"]
最终镜像 12MB,无 shell、无包管理器、无 CVE 来源。这是 Go 微服务的标准姿势。
结语
镜像优化的本质是「只带运行时需要的,不带构建时想要的」。多阶段构建分离编译和运行,Alpine/distroless 缩小基础镜像,合并层减少体积,安全扫描堵住漏洞。一个生产级镜像应该做到:小于 100MB、无高危 CVE、非 root 运行、有健康检查。