Skip to content

镜像优化与瘦身

镜像体积越小,拉取速度越快,部署越迅速,安全攻击面也越小。本节介绍系统性的镜像瘦身技巧。

分析镜像大小

查看基础信息

bash
# 查看镜像大小
docker images myapp

# 查看每一层的大小
docker history myapp

# 详细分层分析(需安装 dive)
dive myapp

安装 dive 分析工具

dive 是一个交互式镜像层分析工具:

bash
# macOS
brew install dive

# Linux
curl -OL https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.tar.gz
tar -xzf dive_linux_amd64.tar.gz
sudo mv dive /usr/local/bin/

# 使用
dive myapp:latest

优化策略一:选择更小的基础镜像

常见镜像大小对比

node:20          ~1.1GB
node:20-bullseye ~1.0GB
node:20-slim     ~250MB
node:20-alpine   ~60MB
dockerfile
# 从 1.1GB 优化到 60MB,只改一行
FROM node:20-alpine  # 而不是 FROM node:20

Distroless 镜像

Google 维护的极简镜像,只包含运行时,不含 shell:

dockerfile
FROM golang:1.21 AS builder
# ... 构建过程 ...

FROM gcr.io/distroless/static:nonroot AS runtime
COPY --from=builder /app /app
USER nonroot
CMD ["/app"]

优化策略二:多阶段构建

参考多阶段构建章节。核心原则:构建工具不进入最终镜像

优化策略三:合并层并清理缓存

dockerfile
# 未优化:每层都有缓存残留
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim

# 优化:合并 + 清理
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
       curl \
       vim \
    && apt-get autoremove -y \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

各包管理器的清理命令

dockerfile
# Alpine APK(--no-cache 直接跳过缓存)
RUN apk add --no-cache curl vim

# npm
RUN npm ci --only=production \
    && npm cache clean --force

# pip
RUN pip install --no-cache-dir -r requirements.txt

# Maven(清理本地仓库缓存)
RUN mvn package -DskipTests \
    && rm -rf /root/.m2/repository

优化策略四:精简 .dockerignore

dockerignore
# 开发依赖(最大的节省)
node_modules/
vendor/
.venv/
__pycache__/

# 版本控制
.git/
.gitignore
.svn/

# 文档和测试
README*.md
docs/
tests/
*.test.js
*.spec.ts

# 构建产物(用于多阶段构建,不需要复制)
dist/
build/
out/

# 敏感文件
.env
.env.*
*.pem
*.key

# 日志
*.log
logs/

# IDE
.vscode/
.idea/
*.swp

优化策略五:只复制必要文件

dockerfile
# 不推荐:复制所有文件
COPY . .

# 推荐:只复制必要的文件
COPY src/ ./src/
COPY package.json package-lock.json ./
COPY tsconfig.json ./

# 或者用 .dockerignore 排除不需要的文件,再 COPY . .

优化策略六:使用 --squash 压缩层(实验性)

bash
# 将所有层压缩为一层(需要开启实验性功能)
docker build --squash -t myapp .

实战:Node.js 镜像从 1.1GB 到 60MB

优化前:

dockerfile
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["node", "app.js"]

大小:约 1.1GB

优化后:

dockerfile
# 阶段一:安装生产依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

# 阶段二:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 阶段三:运行时(只有 Alpine + 代码 + 依赖)
FROM node:20-alpine AS runtime
RUN addgroup -S app && adduser -S app -G app
WORKDIR /app
COPY --from=deps --chown=app:app /app/node_modules ./node_modules
COPY --from=builder --chown=app:app /app/dist ./dist
USER app
EXPOSE 3000
CMD ["node", "dist/app.js"]

大小:约 60MB(节省 94%)

实战:Java 镜像优化

优化前:

dockerfile
FROM maven:3.9-eclipse-temurin-17
# ... 包含完整 JDK + Maven + 源码

大小:约 800MB

优化后:

dockerfile
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 使用 JRE 而非 JDK(仅运行时)
FROM eclipse-temurin:17-jre-alpine AS runtime
WORKDIR /app
COPY --from=builder /build/target/app.jar app.jar
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

大小:约 180MB(节省 77%)

镜像大小参考目标

应用类型未优化优化后目标
Node.js1.1GB60-100MB< 100MB
Python1GB80-150MB< 200MB
Java800MB150-250MB< 300MB
Go800MB5-20MB< 20MB
Nginx 静态200MB20-50MB< 50MB

持续监控镜像大小

在 CI/CD 中加入镜像大小检查:

bash
#!/bin/bash
# check-image-size.sh

IMAGE="myapp:latest"
MAX_SIZE_MB=200

SIZE=$(docker image inspect $IMAGE --format='{{.Size}}')
SIZE_MB=$((SIZE / 1024 / 1024))

echo "Image size: ${SIZE_MB}MB"

if [ $SIZE_MB -gt $MAX_SIZE_MB ]; then
  echo "ERROR: Image size ${SIZE_MB}MB exceeds limit ${MAX_SIZE_MB}MB"
  exit 1
fi

总结

镜像瘦身的优先级策略:

  1. 选择更小的基础镜像(Alpine/slim/distroless)—— 收益最大
  2. 多阶段构建,构建工具不进最终镜像
  3. 合并 RUN 层,安装后清理缓存
  4. 配置 .dockerignore,排除不必要文件
  5. 只复制必要文件

通过以上方法,大多数镜像可以减小 70-95% 的体积。