
超越“能跑就行”
一个在开发中能用的 Dockerfile 在生产环境中往往存在严重问题:镜像体积巨大(本应 50MB 却超过 1GB)、密钥被嵌入层中、以 root 身份运行、仅修改一个文件就需重建所有内容。
高级 Docker 模式系统地解决了这些问题。本指南涵盖了经验丰富的团队在 Docker 镜像投入生产时使用的技术。

多阶段构建
多阶段构建将构建环境与运行环境分离:
# Node.js 示例:构建 TypeScript,运行编译后的 JS
# 阶段 1:依赖
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production # 安装生产依赖
# 分离开发依赖层(更易缓存)
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 # 编译 TypeScript → dist/
# 阶段 3:生产镜像(最小化)
FROM node:20-alpine AS runner
WORKDIR /app
# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
# 仅复制生产所需内容
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./package.json
USER appuser # 不要以 root 运行
EXPOSE 3000
CMD ["node", "dist/index.js"]
结果:构建镜像可能为 800MB。生产镜像:约 150MB。仅部署生产镜像。
Go 多阶段构建(效果更显著)
# Go 生成单个静态二进制文件——最终镜像可小至 ~10MB
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download # 单独缓存依赖
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/app ./cmd/api
# Distroless:无 shell,无包管理器,攻击面最小
FROM gcr.io/distroless/static-debian12 AS runner
COPY --from=builder /bin/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
# 或使用 scratch(绝对最小——仅二进制文件):
FROM scratch
COPY --from=builder /bin/app /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
ENTRYPOINT ["/app"]
层缓存优化
每个 RUN、COPY、ADD 指令都会创建一个层。Docker 会缓存层,并在未发生变化时于重建时重用。顺序很重要:
# ❌ 错误:先复制所有文件——任何文件更改都会使所有层失效
FROM node:20-alpine
WORKDIR /app
COPY . . # 每次构建缓存未命中!
RUN npm ci
RUN npm run build
# ✅ 正确:先复制 package.json——仅当依赖变化时才运行 npm install
FROM node:20-alpine AS builder
WORKDIR /app
# 层 1:这些很少变化 → 保持缓存
COPY package*.json ./
RUN npm ci # 仅当 package.json 变化时运行
# 层 2:这些频繁变化 → 重建,但 npm ci 已缓存
COPY . .
RUN npm run build
# Python 示例,正确缓存
FROM python:3.12-slim AS builder
WORKDIR /app
# 层 1:安装系统依赖(很少变化)
RUN apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*
# 层 2:Python 依赖(当 requirements.txt 变化时变化)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 层 3:应用程序代码(最常变化)
COPY . .
FROM python:3.12-slim AS runner
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /app .
USER nobody
CMD ["python", "app.py"]

BuildKit 密钥(不要将密钥嵌入镜像)
# 问题:私有 npm 仓库需要令牌
# ❌ 错误:令牌最终出现在镜像层中(在 docker history 中可见)
FROM node:20-alpine
ARG NPM_TOKEN # 出现在 docker history 中!
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc
RUN npm install
# 即使之后删除 .npmrc,它仍保留在层中!
# ✅ 正确:使用 BuildKit 密钥——不存储在任何层中
# syntax=docker/dockerfile:1
FROM node:20-alpine
RUN --mount=type=secret,id=npm_token NPM_TOKEN=$(cat /run/secrets/npm_token) && echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > .npmrc && npm install && rm .npmrc
# 密钥从未存储在任何层中!
# 使用密钥构建
docker build --secret id=npm_token,src=~/.npmrc -t my-app .
# 或从环境变量:
docker build --secret id=npm_token,env=NPM_TOKEN -t my-app .
安全加固
以非 root 用户运行
FROM node:20-alpine
# 创建专用用户(不要为自定义应用使用 node 的默认用户)
RUN addgroup --system --gid 1001 appgroup && adduser --system --uid 1001 --ingroup appgroup --no-create-home appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --only=production
# 切换到非 root 用户
USER appuser
EXPOSE 3000
CMD ["node", "server.js"]

只读文件系统
# 在 Kubernetes 部署中
securityContext:
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1001
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"] # 丢弃所有 Linux 能力
# 应用需要写入?挂载特定可写路径
volumeMounts:
- name: tmp-volume
mountPath: /tmp
- name: logs-volume
mountPath: /app/logs
volumes:
- name: tmp-volume
emptyDir: {}
- name: logs-volume
emptyDir: {}
Dockerfile 安全最佳实践
# syntax=docker/dockerfile:1
FROM node:20-alpine
# 固定确切版本——避免使用 :latest 和浮动标签
# 检查:docker scout cves node:20-alpine
# 最小化安装的包
RUN apk add --no-cache curl && rm -rf /var/cache/apk/* # 清理
# 不要在生产中安装开发工具
RUN npm ci --only=production # 不仅仅是 npm install
# 减少攻击面——使用 slim/alpine/distroless 基础镜像
# 层:ubuntu (72MB) > debian-slim (48MB) > alpine (7MB) > distroless (4MB) > scratch (0MB)
# 不要复制 .git、node_modules、密钥等
# .dockerignore 至关重要:
# .dockerignore
.git
.github
node_modules
*.log
.env
.env.*
*.md
tests/
.nyc_output/
coverage/
dist/ # 如果在 Docker 内构建
镜像扫描
# 推送前扫描漏洞
docker scout cves my-app:latest # Docker Desktop 内置
trivy image my-app:latest # 开源,全面
grype my-app:latest # 快速,Anchore 扫描器
# 在 CI/CD 流水线中(GitHub Actions):
# uses: docker/scout-action@v1
# with:
# command: cves
# image: ${{ env.IMAGE }}
# exit-code: true # 关键漏洞时构建失败
BuildKit 缓存挂载
# 跨构建缓存包管理器缓存
# syntax=docker/dockerfile:1
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
# 缓存挂载:跨构建持久化 npm 缓存(大幅加速)
RUN --mount=type=cache,target=/root/.npm npm ci
# Python 使用 pip 缓存
FROM python:3.12-slim
COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
# Go 使用模块缓存
FROM golang:1.22-alpine
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod go mod download
优化的生产 Dockerfile 模板
# syntax=docker/dockerfile:1
# ─── 基础镜像 ───────────────────────────────────
FROM node:20-alpine AS base
WORKDIR /app
ENV NODE_ENV=production
# ─── 依赖 ─────────────────────────────────
FROM base AS deps
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --only=production
FROM base AS devdeps
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
# ─── 构建 ────────────────────────────────────────
FROM devdeps AS builder
COPY . .
RUN npm run build && npm run test:unit
# ─── 安全检查 ───────────────────────────────
FROM deps AS security
RUN npm audit --audit-level=high
# ─── 生产 ───────────────────────────────────
FROM base AS runner
# 元数据标签
LABEL org.opencontainers.image.source="https://github.com/company/repo"
LABEL org.opencontainers.image.revision="${GIT_COMMIT}"
# 非 root 用户
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 --ingroup nodejs nodejs
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/package.json ./
USER nodejs
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
编写良好 Dockerfile 的规范在安全审计、部署速度和运维清晰度方面都会带来回报。小镜像部署更快,攻击面更小,存储和传输成本更低。这里的模式值得尽早内化。
→ 使用 JSON Minifier 工具压缩 JSON 数据。