Skip to content

关于 nextjs DockerFile 更新

较早的配置

dockerfile
# ===== 1. 构建阶段 =====
FROM node:20-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm \
  --registry=https://registry.npmmirror.com
# 仅复制依赖文件,利用缓存
COPY pnpm-lock.yaml package.json ./
# 安装依赖(包括 devDependencies)
RUN pnpm install --frozen-lockfile  \
  --registry=https://registry.npmmirror.com
# 复制项目文件(由 .dockerignore 控制排除)
COPY . .
# 构建 Next.js 应用
RUN pnpm build
# ===== 2. 运行阶段 =====
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN corepack enable

COPY --from=builder /app/package.json ./
COPY --from=builder /app/pnpm-lock.yaml ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.ts ./next.config.ts

EXPOSE 3000
CMD ["pnpm", "start"]

新配置 standalone

js
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;
dockerfile
# 少了 pnpm 作为 PID1、少了运行期依赖不一致、也减少了很多探针误报。
FROM node:20-bookworm-slim AS builder
WORKDIR /app

RUN npm i -g pnpm --registry=https://registry.npmmirror.com
COPY pnpm-lock.yaml package.json ./
RUN pnpm install --frozen-lockfile --registry=https://registry.npmmirror.com

COPY . .
RUN pnpm build

FROM node:20-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production

RUN apt-get update && apt-get install -y tini && rm -rf /var/lib/apt/lists/*

# 拷贝 standalone 输出(不需要整份 node_modules)
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000
ENTRYPOINT ["/usr/bin/tini","--"]
CMD ["node","server.js"]

为什么生产环境更推荐 standalone

  • 不需要完整 node_modules
  • 镜像更小
  • 依赖固定
  • 减少 spawn 子进程
  • 避免 pnpm runtime
  • 启动更快
  • 业界部署通用:很多大厂 Next.js Docker 部署都这么做

为什么 node:20-bookworm-slim 比 node:20-alpine 更推荐

  • Node.js / Next.js / SSR 应用里通常推荐 Debian 系列(bookworm) | 镜像 | libc | 说明 | | --------------------- | ----- | --------------------- | | node:20-bookworm-slim | glibc | 兼容性最好 | | node:20-alpine | musl | 容易出现 native module 问题 |

  • libc: C Standard Library(C 标准库)

  • glibc(GNU libc): Linux默认标准库、兼容、常见、稳定

  • musl(轻量级 libc):小、简单、适合容器、但兼容性稍差

tini 的作用

  • 解决两个 Docker 常见问题:
    • 1、信号转发:容器停止时 docker stop,会发送 SIGTERM,tini 会正确转发给 Node。
    • 2、回收僵尸进程:Node 可能创建子进程 child_process、worker_threads、ffmpeg,退出后会产生 zombie process,tini 会回收这些进程。

ENTRYPOINT 的作用

  • ENTRYPOINT 是 容器启动时的固定命令
  • Docker 启动时执行:ENTRYPOINT + CMD
  • ENTRYPOINT ["/usr/bin/tini","--"] CMD ["node","server.js"] -> /usr/bin/tini -- node server.js
  • 如果没有 tini PID1 = node,如果使用 tini,PID1 = tini,PID2 = node server.js
  • 推荐结构:tini (PID1) └── node server.js