Dockerfile 最佳实践:多阶段构建、镜像优化与安全加固

系统讲解 Dockerfile 的编写最佳实践:多阶段构建(Multi-stage)减少镜像体积、.dockerignore 排除策略、层缓存优化、非 root 用户运行、 secrets 管理、distroless 镜像。含前后端项目的生产级 Dockerfile 模板。

Dockerfile 的写法直接决定了镜像大小、构建速度和安全性。糟糕的 Dockerfile 可能产生 2GB+ 的臃肿镜像,而优化后的版本可以缩小到 100MB 以内。本文覆盖生产级 Dockerfile 的 8 大最佳实践,并提供可直接复用的模板。


一、镜像体积优化

1.1 使用轻量基础镜像

# ❌ 太大(~900MB)
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y nodejs npm

# ✅ Alpine(~50MB)
FROM node:20-alpine

# ✅ 极致:Distroless(~30MB,无 shell,更安全)
FROM gcr.io/distroless/nodejs20-debian12
基础镜像大小特点
Ubuntu~80MB通用,包齐全
Debian Slim~60MB比 Ubuntu 精简
Alpine~5MB最小,musl libc
Distroless~20MBGoogle 出品,无 shell/包管理器

1.2 多阶段构建(Multi-stage)

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

# ===== 运行阶段(只包含运行所需文件) =====
FROM node:20-alpine AS runner
WORKDIR /app

# 只复制构建产物和依赖,不复制源码和构建工具
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

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

多阶段构建的效果:

  • 构建阶段:包含 TypeScript 编译器、开发依赖、源码(>500MB)
  • 运行阶段:只有编译后的 JS 和生产依赖(<100MB)
  • 最终镜像:只包含运行阶段(丢弃构建工具)

1.3 .dockerignore

# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.local
.env.*.local
coverage
.nyc_output
dist           # 如果是编译产物,构建阶段重新生成
build
.vscode
.idea
*.md
Dockerfile
.dockerignore

效果:减少发送到 Docker daemon 的上下文大小,加速构建,减少缓存失效。


二、层缓存优化

2.1 排序原则:变更频繁的放在后面

# ✅ 缓存友好:package.json 变更少,先复制先在
COPY package*.json ./
RUN npm ci --only=production
COPY . .

# ❌ 缓存不友好:每次代码变更都会使 npm install 缓存失效
COPY . .
RUN npm ci --only=production

2.2 合并 RUN 指令

# ❌ 层数多(每个 RUN 创建一个层)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim

# ✅ 合并为一层
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    && rm -rf /var/lib/apt/lists/*

三、安全加固

3.1 不以 root 运行

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 切换用户
USER nextjs

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

3.2 使用只读文件系统

docker run --read-only -v /tmp:/tmp my-app

3.3 安全扫描

# Docker 内置扫描
docker scan my-app

# Trivy(推荐)
trivy image my-app

四、生产级 Dockerfile 模板

Next.js(App Router)

# syntax=docker/dockerfile:1

FROM node:20-alpine AS base

# 安装依赖阶段
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package*.json ./
RUN npm ci

# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

# 运行阶段
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]

注意:Next.js 需要 output: 'standalone' 配置。

Node.js Express API

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
CMD ["node", "dist/main.js"]

Python FastAPI

FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY ./app ./app
ENV PATH=/root/.local/bin:$PATH
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

五、构建优化 CLI

# 构建时显示层大小
docker build --progress=plain -t my-app .

# 查看镜像历史(每层大小)
docker history my-app

# 分析镜像内容
docker run --rm -it my-app sh
du -sh /app/*

# 多平台构建
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t my-app .

常见问题(FAQ)

多阶段构建会更快吗?

不一定更快,但最终镜像一定更小。构建速度取决于缓存命中率,多阶段构建通过分离依赖安装和代码复制,通常能提升缓存效率。

Alpine 的 musl libc 会有兼容性问题吗?

大部分 Node.js/Python 应用没问题。但如果依赖原生 C++ 扩展(如 sharp 图片库、bcrypt),可能需要额外安装 build-base:

RUN apk add --no-cache python3 make g++

Distroless 镜像如何调试?

Distroless 没有 shell,调试困难。建议:

  1. 开发测试用 Alpine
  2. 生产用 Distroless
  3. 需要调试时临时切换到 Alpine:
    docker run --rm -it --entrypoint=sh my-app:debug

相关阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「saas」更多文章