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 | ~20MB | Google 出品,无 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,调试困难。建议:
- 开发测试用 Alpine
- 生产用 Distroless
- 需要调试时临时切换到 Alpine:
docker run --rm -it --entrypoint=sh my-app:debug
相关阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。
「saas」更多文章
自定义域名接入 Cloudflare:CDN 加速与缓存规则配置实战
手把手讲解自定义域名接入 Cloudflare CDN 的完整流程:NS 接管、SSL/TLS 模式选择、橙云代理与灰云 DNS 的区别,并用 2024 年后的新 Cache Rules 引擎实战配置静态资源长缓存、HTML 不缓存、后台 Bypass,让边缘缓存命中率最大化。
Hugo 部署到 Cloudflare Pages 实战:从 Git 推送到全球边缘上线
详解 Cloudflare Pages 部署 Hugo 静态站的完整流程,涵盖框架预设、HUGO_VERSION 环境变量固定、构建命令、自定义域名、分支预览、_headers 缓存安全头配置及常见构建失败排查,一次推送即可全球边缘上线。
Cloudflare Workers 入门实战:在边缘运行你的第一行代码
一份面向初学者的 cloudflare workers 教程,从 V8 Isolate 运行时模型讲起,手把手演示 wrangler 初始化、路由分发、KV 存储、Cache API 缓存响应与环境变量配置,帮你在 30 分钟内部署第一个生产可用的边缘 Serverless 应用。