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 运行、有健康检查。