Skip to content
返回构建教程

Docker 入门

写多阶段 Dockerfile,产出小且可缓存的生产镜像。

为什么

朴素 Dockerfile 把 node_modules 拷进镜像,产出 1.2 GB 块,推送要 4 分钟。多阶段构建把安装(重、可缓存)与运行时(轻、部署快)分开,镜像降到 150 MB,推送 20 秒。层缓存意味着只改代码的重建 5 秒而非 3 分钟,因为依赖层被复用。这在每次部署都重要:4 分钟推送 × 10 次/天 = 40 分钟 CI 浪费,1.2 GB 镜像每次 registry 拉取存储和传输贵 10 倍。

何时使用

需要部署到容器运行时(k8s、ECS、Fly、Cloud Run)时。约 45-60 分钟。不使用容器的 serverless/edge 部署可跳过。

如何做

1. 用多阶段构建:deps → build → runtime
   - FROM node:20-slim AS deps(用 --frozen-lockfile 安装)
   - FROM deps AS builder(拷源码,跑构建)
   - FROM node:20-slim AS runner(只拷 dist + node_modules/prod)

2. 层从最少变到最多变排序以命中缓存
   - COPY package.json pnpm-lock.yaml → pnpm install(lockfile 不变即缓存)
   - COPY . . → build(任何源码变即破缓存,但 deps 层存活)

3. 用 .dockerignore 排除 node_modules、.next、.git、测试
   - 没它,构建上下文 500 MB,每次构建都发给 daemon

4. 以非 root 用户运行(USER node)并设 HEALTHCHECK
   - 多数基础镜像带 'node' 用户;CMD 前切到它
   - HEALTHCHECK CMD curl -f http://localhost:3000/api/health || exit 1

5. 用 buildx + .env 本地构建测试
   - docker buildx build --target runner -t myapp:latest .
   - docker run --env-file .env -p 3000:3000 myapp:latest

示例

# Dockerfile(多阶段,Next.js)
FROM node:20-slim AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile --prod

FROM deps AS builder
COPY . .
RUN pnpm build

FROM node:20-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]

常见问题

  • ⚠拷整个仓库含 .git 和 node_modules,构建上下文 1 GB,第一层发送要 2 分钟。
  • ⚠用 node:20(完整)而非 node:20-slim 或 alpine,附带 400 MB 应用从不用的 Debian 包。
  • ⚠因基础镜像默认 root 而以 root 运行——容器逃逸变宿主 root。
  • ⚠在 pnpm install 前 COPY . . 导致每次代码变更都破 deps 缓存。

最佳实践

  • ✓锁基础镜像 digest(FROM node:20-slim@sha256:abc...)以可复现构建——tag 会动,digest 不会。
  • ✓用 BuildKit 缓存挂载(RUN --mount=type=cache,target=/root/.pnpm-store)跨构建共享包缓存。
  • ✓CI 里用 Trivy 或 Grype 扫最终镜像——0 个严重 CVE 的 150 MB 镜像胜过 12 个的 50 MB。
  • ✓运行时设内存限制(docker run --memory=512m),泄漏让容器失败而非宿主。