Docker: 镜像优化与最佳实践

最后更新:2026-08-26

优化镜像不只是减小体积——更小的镜像意味着更快的部署、更低的存储成本和更小的攻击面。

1. 你将学到


2. 一个 CI 工程师的真实故事

(1) 痛点:CI 构建时间从 8 分钟飙升到 25 分钟

Bob 的 CI 构建时间突然从 8 分钟飙升到 25 分钟。他排查发现,每次构建都重新安装所有依赖——因为 COPY . . 放在 RUN pip install 前面,任何代码变更都导致依赖安装层缓存失效。20 分钟的等待让开发效率大幅下降。

(2) 缓存优化的解法

调整 Dockerfile 指令顺序,把不常变的 COPY package.json 提前,利用层缓存把构建时间降回 3 分钟。

DOCKERFILE
# Before: code change invalidates pip cache
COPY . .
RUN pip install -r requirements.txt

# After: dependency change rate << code change rate
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .

(3) 收益:构建时间 25 分钟→3 分钟

只调整了两行代码顺序,构建时间从 25 分钟降到 3 分钟(缓存命中时),CI 管道效率提升 8 倍。


3. 镜像优化核心策略

(1) 优化前后对比框架

100%
graph TB
    subgraph Before["Before Optimization"]
        B1["FROM ubuntu:22.04<br/>77 MB"] --> B2["RUN apt-get install<br/>300 MB"]
        B2 --> B3["COPY . .<br/>含 node_modules"]
        B3 --> B4["RUN npm install<br/>200 MB"]
        B4 --> B5["Final: 1.2 GB"]
    end
    subgraph After["After Optimization"]
        A1["FROM node:20-alpine<br/>135 MB"] --> A2["COPY package.json"]
        A2 --> A3["RUN npm ci<br/>50 MB"]
        A3 --> A4["COPY . .<br/>无 node_modules"]
        A4 --> A5["Final: 180 MB"]
    end

(2) 优化策略总览

策略 效果 实施难度
更换基础镜像(alpine/slim) 体积↓50-80%
多阶段构建 体积↓60-90% ⭐⭐
RUN 合并 + 清理缓存 体积↓20-40%
调整 COPY 顺序 构建速度↑3-8x
.dockerignore 上下文↓30-90%
hadolint 检查 发现潜在问题 ⭐⭐

4. RUN 指令合并

(1) 合并原则

每条 RUN 指令创建一个新层。合并相关操作到一个 RUN 中,并在同层内清理临时文件。

▶ 示例:指令合并(难度⭐⭐)

DOCKERFILE
# Bad: 3 separate layers, apt cache stays in image
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y nginx

# Good: 1 layer, cache cleaned within the same layer
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        curl \
        nginx && \
    rm -rf /var/lib/apt/lists/*

(2) --no-install-recommends 的效果

选项 安装内容 典型体积节省
默认 主包 + 推荐包 + 建议包 基线
--no-install-recommends 仅主包 + 依赖 ↓30-50%

5. 缓存命中率优化

(1) 层缓存机制

Docker 构建镜像时,如果某层的指令和上下文未变,就复用缓存。一旦某层缓存失效,后续所有层都必须重建。

100%
graph TB
    L1["FROM python:3.12<br/>✅ Cache Hit"] --> L2["WORKDIR /app<br/>✅ Cache Hit"]
    L2 --> L3["COPY requirements.txt<br/>✅ Cache Hit (no change)"]
    L3 --> L4["RUN pip install<br/>✅ Cache Hit"]
    L4 --> L5["COPY . .<br/>❌ Cache Miss (code changed)"]
    L5 --> L6["CMD python app.py<br/>❌ Rebuild"]

▶ 示例:--no-cache 对比构建(难度⭐⭐)

BASH
# Normal build: uses cache where possible
docker build -t myapp:1.0 .

# Force rebuild without any cache
docker build --no-cache -t myapp:1.0 .

# Use a previous image as cache source (CI optimization)
docker build --cache-from=myapp:latest -t myapp:1.0 .

(2) 优化 Dockerfile 指令顺序

DOCKERFILE
# ============================================
# Optimized Dockerfile: high cache hit rate
# ============================================

FROM node:20-alpine

WORKDIR /app

# 1. Least frequently changed: package metadata
COPY package*.json ./

# 2. Dependency installation (cached unless package.json changes)
RUN npm ci --only=production

# 3. Most frequently changed: application code
COPY . .

EXPOSE 3000
CMD ["node", "server.js"]
变更场景 优化版 未优化版
只改代码 ✅ npm ci 用缓存(5 秒) ❌ npm ci 重建(3 分钟)
改依赖版本 ✅ npm ci 重建(必要) ❌ npm ci 重建(同上)
改 Dockerfile ✅ 只重建受影响的层 ❌ 全部重建

6. 镜像分析工具

▶ 示例:用 dive 分析镜像层(难度⭐⭐)

dive 是一个交互式镜像层分析工具,显示每一层添加/修改/删除了哪些文件。

BASH
# Install dive (Linux)
wget https://github.com/wagoodman/dive/releases/download/v0.12.0/dive_0.12.0_linux_amd64.deb
sudo dpkg -i dive_0.12.0_linux_amd64.deb

# Analyze an image
dive nginx:latest
💡 提示: dive 的界面显示:① 左侧每层的大小和效率评分;② 右侧每层添加的文件树。红色标记浪费空间(被后续层覆盖/删除的文件)。

▶ 示例:用 hadolint 检查 Dockerfile(难度⭐⭐)

hadolint 是 Dockerfile 的 Lint 工具,检查最佳实践违规。

BASH
# Install hadolint (Linux)
wget -O /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/v2.12.0/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint

# Check a Dockerfile
hadolint Dockerfile
💻 输出:

TEXT 📖 仅展示
Dockerfile:3 DL3013 warning: Pin versions in pip. Instead of `pip install <package>` use `pip install <package>==<version>`
Dockerfile:5 DL3059 info: Do not use `--no-cache-dir` in pip install. It is redundant when `--cache-from` is used.
Dockerfile:7 DL3008 warning: Pin versions in apt-get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`

7. 优化前后 Dockerfile 完整对比

(1) Node.js 应用优化示例

DOCKERFILE
# ============================================
# BEFORE: Unoptimized Dockerfile (1.2 GB)
# ============================================
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
EXPOSE 3000
CMD ["npm", "start"]
DOCKERFILE
# ============================================
# AFTER: Optimized Dockerfile (180 MB)
# ============================================
# 1. Alpine base instead of full Node
FROM node:20-alpine

WORKDIR /app

# 2. Copy dependency metadata first (cache optimization)
COPY package*.json ./

# 3. Production-only dependencies
RUN npm ci --only=production && \
    npm cache clean --force

# 4. Copy source code (most frequently changed)
COPY . .

# 5. Run as non-root user
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup && \
    chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "server.js"]

(2) 优化效果对比

指标 优化前 优化后 改善
镜像体积 1.2 GB 180 MB 6.7x ↓
构建时间(代码变更) 3 分钟 5 秒 36x ↓
构建时间(依赖变更) 3 分钟 45 秒 4x ↓
安全漏洞 200+ 30 6.7x ↓
CIS Benchmark 评分 45 85 +40

8. 各语言依赖安装策略

语言 依赖文件 安装命令 清理命令
Node.js package*.json npm ci --only=production npm cache clean --force
Python requirements.txt pip install --no-cache-dir (内置 --no-cache-dir)
Go go.mod go.sum go mod download go clean -modcache
Java pom.xml / build.gradle mvn dependency:resolve 清理 .m2 缓存
Ruby Gemfile Gemfile.lock bundle install --without dev 清理 .bundle 缓存

9. 完整示例:Node.js 应用全流程优化

DOCKERFILE
# ============================================
# Fully optimized Node.js production Dockerfile
# Features: alpine, multi-stage, cache, non-root
# ============================================

# ---------- Stage 1: Build ----------
FROM node:20-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# ---------- Stage 2: Production ----------
FROM node:20-alpine

WORKDIR /app

# Copy only production dependencies
COPY package*.json ./
RUN npm ci --only=production && \
    npm cache clean --force

# Copy built application from builder
COPY --from=builder /app/dist ./dist

# Security: non-root user
RUN addgroup -S appgroup && \
    adduser -S appuser -G appgroup && \
    chown -R appuser:appgroup /app
USER appuser

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]
BASH
# Build and verify
docker build -t node-app:optimized .

# Compare sizes
docker images node-app

# Analyze with dive
dive node-app:optimized

# Check with hadolint
hadolint Dockerfile

❓ 常见问题

Q 每 RUN 一层都会增加镜像大小吗?
A 是的,即使 RUN 只是删除文件——因为新层记录"删除操作",而底层仍然存在。所以"安装+清理"必须在同一个 RUN 中完成,这样安装的临时文件和删除操作在同一个层里,底层看不到临时文件。
Q apt-get clean 为什么能减镜像体积?
A 单独的 apt-get clean 几乎无效(和 rm 同理)。必须在同一个 RUN 中:apt-get update && apt-get install && rm -rf /var/lib/apt/lists/*--no-install-recommendsapt-get clean 效果更大。
Q 怎么确认缓存是否被命中?
A docker build 输出中:Using cache 表示命中,Step 3/7 : RUN xxx 表示重建。也可以用 --progress=plain 查看详细日志。CI 中 docker build --cache-from=app:latest 可复用上次构建的缓存。
Q --no-cache 什么时候用?
A 两种场景:① 基础镜像有安全更新需要重新构建;② 构建结果异常怀疑缓存损坏。日常开发不要用 --no-cache,会让每次构建都从零开始。
Q 如何检测镜像里的安全漏洞?
A docker scout quickview <image>(Docker 官方)或 trivy image <image>(开源)。Trivy 是最流行的开源镜像扫描工具,可检测 OS 包和语言依赖的已知 CVE。建议在 CI 管道中集成扫描步骤。

📖 小节


📝 作业

  1. 基础题(难度⭐):用 dive 分析一个已有镜像(如 nginx:latest)的每一层,记录哪一层最大、添加了什么文件。
  2. 进阶题(难度⭐⭐):用 hadolint 检查第 7-9 课的 Dockerfile,修复所有 warning 级别的建议。
  3. 挑战题(难度⭐⭐⭐):对比 alpine/debian/slim 三种基镜像构建同一个 Python 应用的镜像大小差异,分析体积差异的原因。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏