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),泄漏让容器失败而非宿主。