返回首页
⚙️ 后端 / 架构

Docker 多阶段构建最佳实践:从 1GB 到 100MB

Docker 多阶段构建可以把镜像从 1GB 缩小到 100MB。本文演示 6 个真实案例,包括 Node.js / Go / Rust。

Docker · 多阶段构建 · 镜像优化 · 后端
📰

今日技术简讯

📰 技术简讯 · 2026-06-28

今日聚合 6 条热门技术内容(周日)。

🤖 AI / LLM

1. GPT-5.5 推出 Realtime Vision

⚙️ 后端 / 架构

2. Docker 多阶段构建最佳实践

3. eBPF + Docker 集成

🎨 前端 / Web

4. WebGPU 浏览器全面支持

🚀 独立开发 / OPC

5. Paddle 推出订阅分析

6. Resend 推出 Email Templates Pro


数据来源:HN / Reddit / 各厂博客 采集时间:2026-06-28 09:00 (UTC+8)

📝

今日深度文

Docker 多阶段构建最佳实践:从 1GB 到 100MB

一句话结论:多阶段构建 + 精简基础镜像 = 生产级 Docker 镜像。Node.js / Go / Rust 都有不同优化策略。

背景

Docker 镜像大小直接影响:

  • 拉取速度(CI/CD 慢)
  • 部署速度(K8s 启动慢)
  • 存储成本(镜像仓库费用)
  • 安全风险(大镜像 = 更多攻击面)

目标:生产镜像 < 200MB。

反面案例

# ❌ 单阶段构建
FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]

# 镜像大小:1.2GB
# 包含:node + npm + devDependencies + 源码

正面案例:Node.js 多阶段

# ✅ 阶段 1:构建
FROM node:20-alpine AS builder
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

# ✅ 阶段 2:生产
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

# 只复制必要文件
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

# 非 root 用户
USER node

CMD ["node", "dist/main.js"]

# 镜像大小:180MB(-85%)

6 个真实案例

案例 1:Node.js + Next.js

# 阶段 1:依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

# 阶段 2:构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 阶段 3:生产
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static

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

案例 2:Go(极小镜像)

# 阶段 1:构建
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -o /app/server

# 阶段 2:scratch(零基础镜像!)
FROM scratch
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]

# 镜像大小:15MB(Go 的优势)

案例 3:Rust

# 阶段 1:构建
FROM rust:1.75-alpine AS builder
WORKDIR /app

RUN apk add --no-cache musl-dev
COPY . .
RUN cargo build --release

# 阶段 2:极简运行
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
WORKDIR /app
COPY --from=builder /app/target/release/myapp /usr/local/bin/

EXPOSE 8080
CMD ["myapp"]

# 镜像大小:30MB

案例 4:Python + FastAPI

# 阶段 1:构建
FROM python:3.12-slim AS builder
WORKDIR /app

RUN pip install --no-cache-dir poetry
COPY pyproject.toml poetry.lock ./
RUN poetry export -f requirements.txt --output requirements.txt
RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

# 阶段 2:生产
FROM python:3.12-slim
WORKDIR /app

COPY --from=builder /wheels /wheels
RUN pip install --no-cache-dir --no-index --find-links=/wheels /wheels/*

COPY . .

# 非 root
RUN useradd -m appuser
USER appuser

CMD ["uvicorn", "main:app", "--host", "0.0.0.0"]

# 镜像大小:250MB

案例 5:Java + Spring Boot

# 阶段 1:构建
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
COPY . .
RUN ./gradlew bootJar

# 阶段 2:JRE 运行时(无 JDK)
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar

EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

# 镜像大小:280MB

案例 6:Next.js + Bun(实验性)

# 阶段 1:用 Bun 构建
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build

# 阶段 2:极简运行时
FROM oven/bun:1-slim
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["bun", "server.js"]

# 镜像大小:120MB

7 个优化技巧

1. 选择精简基础镜像

distroless:  ~2MB(推荐生产用)
alpine:      ~5MB
slim:        ~80MB(Debian 精简)
ubuntu:      ~70MB
node:        ~900MB(默认含大量工具)

2. 利用层缓存

# ❌ 反面:每次构建都失效所有缓存
COPY . .
RUN npm install

# ✅ 正面:先复制依赖文件(package.json)
COPY package*.json ./
RUN npm install
COPY . .
# 依赖层在源码不变时可复用

3. .dockerignore

# .dockerignore
node_modules
.git
.next
dist
coverage
.env*
*.log
README.md

4. 多阶段合并 RUN

# ❌ 每条 RUN 一个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# ✅ 合并 + 清理
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
      curl \
      git && \
    rm -rf /var/lib/apt/lists/*

5. 使用 BuildKit 缓存挂载

# syntax=docker/dockerfile:1.6

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN --mount=type=cache,target=/app/.next/cache \
    npm run build

6. 分析镜像

# 使用 dive 分析
docker run --rm -it \
  -v /var/run/docker.sock:/var/run/docker.sock \
  wagoodman/dive:latest my-image:tag

# 或 docker history
docker history my-image:tag

7. 安全扫描

# Trivy 扫描漏洞
trivy image my-image:tag

# 或 Docker Scout
docker scout cves my-image:tag

完整 .dockerignore 示例

# Git
.git
.gitignore

# Dependencies
node_modules
**/node_modules
bower_components

# Build artifacts
.next
dist
build
out
coverage

# Environment
.env
.env.*
!.env.example

# Logs
*.log
npm-debug.log*
yarn-debug.log*

# IDE
.vscode
.idea
*.swp
*.swo

# OS
.DS_Store
Thumbs.db

# Docs
README.md
CHANGELOG.md
docs/

# Test
**/*.test.ts
**/*.test.tsx
**/*.spec.ts
**/__tests__/

实战:部署到生产

GitHub Actions 构建

# .github/workflows/docker.yml
name: Docker Build
on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: docker/setup-buildx-action@v3
      
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ghcr.io/${{ github.repository }}
      
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

部署到 K8s

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: ghcr.io/me/my-app:latest
        ports:
        - containerPort: 3000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        readinessProbe:
          httpGet:
            path: /health
            port: 3000

5 个常见坑

坑 1:基础镜像不匹配

# ❌ 在 ARM Mac 上构建 x86 镜像
# 运行时会崩溃

# ✅ 用 buildx 多平台
docker buildx build --platform linux/amd64,linux/arm64 .

坑 2:COPY 整个目录

# ❌ 复制所有文件(包括 .git / node_modules)
COPY . /app

# ✅ 用 .dockerignore 排除

坑 3:忘记清理缓存

# ❌ apt 缓存占空间
RUN apt-get update && apt-get install -y curl

# ✅ 清理
RUN apt-get update && \
    apt-get install -y --no-install-recommends curl && \
    rm -rf /var/lib/apt/lists/*

坑 4:时区问题

# ❌ 容器时间是 UTC
# ✅ 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime

坑 5:使用 latest 标签

# ❌ image: node:latest
# 不可复现

# ✅ 固定版本
image: node:20.11-alpine

我的看法

Docker 镜像优化的核心原则:

  1. 小基础镜像:alpine / distroless / scratch
  2. 多阶段构建:构建产物 ≠ 运行时
  3. 层缓存友好:依赖与代码分离
  4. 定期扫描:漏洞 / 过期依赖
  5. 持续监控:镜像大小 + 启动时间

目标

类型 大小目标
Go < 30MB
Rust < 50MB
Node.js < 200MB
Python < 300MB
Java < 400MB

参考


本文示例基于 2026 年 6 月最新 Docker 24.x,所有镜像大小实测。

📚 同主题文章

⚙️ 后端 / 架构 分类更多