next打包docker镜像

半兽人 发表于: 2025-06-19   最后更新时间: 2025-06-23 09:27:00  
{{totalSubscript}} 订阅, 72 游览

Dockerfile 采用了多阶段构建(multi-stage build)的方式,构建并部署一个基于 Node.js + pnpm 的 Web 应用(如 Next.js 项目),优化了镜像体积和构建速度,并特别适配国内的网络环境。

# ==========================================================================
# base image
# ==========================================================================

FROM node:20-alpine3.20 AS base

# if you located in China, you can use aliyun mirror to speed up
# RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

RUN apk add --no-cache tzdata
RUN npm install -g pnpm@9.12.2
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"

# ==========================================================================
# install packages
# ==========================================================================

FROM base AS packages

WORKDIR /

COPY package.json .
COPY pnpm-lock.yaml .

# if you located in China, you can use taobao registry to speed up
# RUN pnpm install --frozen-lockfile --registry https://registry.npmmirror.com/

RUN pnpm install --frozen-lockfile

# ==========================================================================
# build resources
# ==========================================================================

FROM base AS builder
WORKDIR /app/web
COPY --from=packages / .
COPY . .

ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN pnpm build

# ==========================================================================
# production stage
# ==========================================================================

FROM base AS production

ENV NODE_ENV=production
ENV EDITION=SELF_HOSTED
ENV DEPLOY_ENV=PRODUCTION
ENV CONSOLE_API_URL=http://127.0.0.1:5001
ENV APP_API_URL=http://127.0.0.1:5001
ENV MARKETPLACE_API_URL=http://127.0.0.1:5001
ENV MARKETPLACE_URL=http://127.0.0.1:5001
ENV PORT=3000
ENV NEXT_TELEMETRY_DISABLED=1
ENV PM2_INSTANCES=2

# set timezone
ENV TZ=UTC
RUN ln -s /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone


WORKDIR /app/web
COPY --from=builder /app/web/public ./public
COPY --from=builder /app/web/.next/standalone ./
COPY --from=builder /app/web/.next/static ./.next/static

COPY docker/entrypoint.sh ./entrypoint.sh

# ==========================================================================
# global runtime packages
# ==========================================================================

RUN pnpm add -g pm2 \
    && mkdir /.pm2 \
    && chown -R 1001:0 /.pm2 /app/web \
    && chmod -R g=u /.pm2 /app/web

ARG COMMIT_SHA
ENV COMMIT_SHA=${COMMIT_SHA}

USER 1001
EXPOSE 3000
ENTRYPOINT ["/bin/sh", "./entrypoint.sh"]

总体结构(多阶段构建)

共分为 四个阶段

阶段名 作用
base 定义基础环境:Node.js、Alpine、pnpm、镜像源等
packages 安装依赖(package.json、pnpm-lock.yaml)
builder 拷贝源码、构建项目(如 Next.js)
production 最终部署环境,最小化体积,仅保留构建产物和运行依赖

各阶段详解

FROM node:20-alpine3.20 AS base

基础镜像使用 Node.js v20 + Alpine 3.20,适用于构建轻量级镜像。

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories

将 Alpine 的源切换到阿里云,加快国内构建速度。

RUN apk add --no-cache tzdata

安装时区数据包(tzdata),后续可以设置时区。

RUN npm install -g pnpm@9.12.2
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"

全局安装 pnpm,设置环境变量,支持构建依赖。

FROM base AS packages

用于安装项目依赖,这个阶段只做依赖缓存,减少后续构建重复下载。

WORKDIR /

COPY package.json .
COPY pnpm-lock.yaml .

RUN pnpm install --frozen-lockfile --registry https://registry.npmmirror.com/

拷贝 lock 文件,锁定依赖版本,并使用淘宝镜像加速。此时不会拷贝源码,只处理依赖。

FROM base AS builder

用于正式构建项目

WORKDIR /app/web

COPY --from=packages / .      # 拷贝 node_modules 等依赖
COPY . .                      # 拷贝项目源码

设置最大内存 4GB:

ENV NODE_OPTIONS="--max-old-space-size=4096"

执行构建:

RUN pnpm build

这一步将生成 .next 构建产物(静态文件、server 代码等)。

FROM base AS production

最终的部署镜像,构建产物被拷贝到此处。

设置环境变量:

ENV NODE_ENV=production
ENV EDITION=SELF_HOSTED
...
ENV PORT=3000
ENV NEXT_TELEMETRY_DISABLED=1
ENV PM2_INSTANCES=2

时区设置为 UTC:

ENV TZ=UTC
RUN ln -s /usr/share/zoneinfo/${TZ} /etc/localtime \
    && echo ${TZ} > /etc/timezone

拷贝运行所需文件(非整个项目):

COPY --from=builder /app/web/public ./public
COPY --from=builder /app/web/.next/standalone ./
COPY --from=builder /app/web/.next/static ./.next/static
COPY docker/entrypoint.sh ./entrypoint.sh

添加 pm2,并设置目录权限:

RUN pnpm add -g pm2 \
    && mkdir /.pm2 \
    && chown -R 1001:0 /.pm2 /app/web \
    && chmod -R g=u /.pm2 /app/web

设置构建时参数 COMMIT_SHA

ARG COMMIT_SHA
ENV COMMIT_SHA=${COMMIT_SHA}

切换运行用户为非 root 用户(1001),暴露 3000 端口:

USER 1001
EXPOSE 3000
ENTRYPOINT ["/bin/sh", "./entrypoint.sh"]

最终效果:

这个 Dockerfile 会生成一个用于生产部署的最小 Node.js 镜像,特点是:

  • 构建快(使用镜像加速与多阶段缓存)
  • 运行轻(只包含 .next 产物和静态资源)
  • 易于部署(入口脚本控制进程启动,如用 pm2)
  • 国内网络适配良好

entrypoint.sh

entrypoint.sh 脚本就是 Docker 镜像的启动脚本,它的作用是:

#!/bin/bash

# if you are using windows, you may need to convert the file to unix format
# you can use the Ubuntu terminal to convert this file to unix format
# otherwise, you may get the error after running the docker container

# sudo apt-get install dos2unix
# dos2unix entrypoint.sh


set -e

export NEXT_PUBLIC_DEPLOY_ENV=${DEPLOY_ENV}
export NEXT_PUBLIC_EDITION=${EDITION}
export NEXT_PUBLIC_API_PREFIX=${CONSOLE_API_URL}/console/api
export NEXT_PUBLIC_PUBLIC_API_PREFIX=${APP_API_URL}/api
export NEXT_PUBLIC_MARKETPLACE_API_PREFIX=${MARKETPLACE_API_URL}/api/v1
export NEXT_PUBLIC_MARKETPLACE_URL_PREFIX=${MARKETPLACE_URL}

export NEXT_PUBLIC_SENTRY_DSN=${SENTRY_DSN}
export NEXT_PUBLIC_SITE_ABOUT=${SITE_ABOUT}
export NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED}

export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}

pm2 start /app/web/server.js --name dify-web --cwd /app/web -i ${PM2_INSTANCES} --no-daemon
  1. 设置必要的环境变量(NEXT_PUBLIC_ 这些是给前端用的)
  2. 启动 PM2 来运行 Next.js 应用(通过 .next/standalone/server.js

下面我们来逐行讲解:

一、文件格式声明和兼容性说明

#!/bin/bash

声明该脚本使用 bash 执行。

# if you are using windows, you may need to convert the file to unix format
# you can use the Ubuntu terminal to convert this file to unix format
# otherwise, you may get the error after running the docker container

# sudo apt-get install dos2unix
# dos2unix entrypoint.sh

说明:如果你在 Windows 上创建该文件,会携带 CRLF 换行符,Linux 执行会报错(例如 bash^M: not found)。所以推荐用 dos2unix 转换格式。

二、错误处理

set -e

含义:脚本执行中如果任一命令出错(返回非 0),立即退出,避免继续运行。

三、环境变量注入(给 Next.js 使用)

export NEXT_PUBLIC_DEPLOY_ENV=${DEPLOY_ENV}
export NEXT_PUBLIC_EDITION=${EDITION}
export NEXT_PUBLIC_API_PREFIX=${CONSOLE_API_URL}/console/api
export NEXT_PUBLIC_PUBLIC_API_PREFIX=${APP_API_URL}/api
export NEXT_PUBLIC_MARKETPLACE_API_PREFIX=${MARKETPLACE_API_URL}/api/v1
export NEXT_PUBLIC_MARKETPLACE_URL_PREFIX=${MARKETPLACE_URL}

这些变量会被暴露给 前端页面,通过 NEXT_PUBLIC_ 这个前缀传递到浏览器中。比如:

  • NEXT_PUBLIC_API_PREFIX 会变成浏览器访问后端的接口前缀
  • NEXT_PUBLIC_DEPLOY_ENV 表示部署环境(如 PRODUCTION)
  • NEXT_PUBLIC_MARKETPLACE_API_PREFIX 是商城相关接口地址

接下来是监控和页面配置:

export NEXT_PUBLIC_SENTRY_DSN=${SENTRY_DSN}
export NEXT_PUBLIC_SITE_ABOUT=${SITE_ABOUT}
export NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED}

其他配置项,都是用于前端参数控制:

export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}

四、启动 PM2 运行 .next/standalone/server.js

pm2 start /app/web/server.js \
  --name dify-web \
  --cwd /app/web \
  -i ${PM2_INSTANCES} \
  --no-daemon

解释:

参数 说明
/app/web/server.js Next.js 编译后的服务端入口(由 output: standalone 自动生成)
--name dify-web 给 PM2 设置进程名
--cwd /app/web 设置当前工作目录(更好地识别路径)
-i ${PM2_INSTANCES} 多实例模式,${PM2_INSTANCES} 变量在 Dockerfile 中默认是 2
--no-daemon 前台运行(防止 PM2 自己变为后台进程,否则容器会退出)

这样容器启动后,会:

  • 用设置的环境变量启动一个 Next.js 应用
  • 使用 PM2 多进程处理流量
  • 绑定在 3000 端口上(由 Dockerfile 的 EXPOSE 3000 指定)

总结:entrypoint.sh 的作用

功能 说明
环境变量注入 .env 配置转换成 NEXT_PUBLIC_* 的形式供 Next.js 用
启动应用 用 PM2 启动构建产物 server.js,并发运行,支持自动重启
兼容性说明 提醒 Windows 用户注意换行符问题

扩展建议(可选)

你可以在脚本里加入打印日志,便于调试:

echo "Starting Dify Web with environment: $DEPLOY_ENV"
echo "Console API: $CONSOLE_API_URL"

或者处理空变量问题,例如:

: "${PM2_INSTANCES:=2}"  # 如果没有设置,则默认2
更新于 2025-06-23
在线,1小时前登录

查看Next.js更多相关的文章或提一个关于Next.js的问题,也可以与我们一起分享文章