Docker 部署 json4u 项目问题总结与解决方案
写在前面
最近在小红书上看到一个名为json4u的开源项目,感觉挺有意思的。考虑到日常工作涉及大量JSON数据处理,并且出于对数据安全和效率提升的考虑,本地化部署显然比在线工具更加可靠和高效。这个项目看起来非常实用,尤其是对于需要频繁处理JSON数据的场景,应该能带来不少便利。相比在线工具,本地部署不仅降低了数据泄露的风险,还能提高工作效率,所以准备部署一把尝试一下。
项目简介
json4u 是一个开源、全功能的 JSON 可视化与处理工具。它能将 JSON 数据以直观的树状图或思维导图形式展示,并支持格式化、转义、压缩等操作。虽然我曾使用 pnpm 成功部署过该项目,但在 Docker 部署过程中遇到了一些问题,特别是关于构建产物与环境变量加载的问题。本文总结了这些问题和解决方案,希望这篇总结对你未来的部署工作有所帮助。

1. 依赖安装问题:绕过 Corepack
问题描述
最初使用 Corepack 调用 pnpm 安装依赖时,出现签名验证错误,导致构建失败。
原Dockerfile:
# build stage FROM node:20-alpine AS base # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json pnpm-lock.yaml* ./ RUN corepack enable pnpm && pnpm i --frozen-lockfile # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ARG APP_URL=http://localhost.json4u.cn:3000 ARG FREE_QUOTA=99 ARG SENTRY_AUTH_TOKEN= ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN ENV NEXT_TELEMETRY_DISABLED=1 ENV NODE_ENV=production ENV NEXT_PUBLIC_APP_URL=$APP_URL ENV NEXT_PUBLIC_FREE_QUOTA="{\"graphModeView\":$FREE_QUOTA,\"tableModeView\":$FREE_QUOTA,\"textComparison\":$FREE_QUOTA,\"jqExecutions\":$FREE_QUOTA}" RUN corepack enable pnpm && pnpm run build # Production image, copy all the files and run next FROM base AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Set the correct permission for prerender cache RUN mkdir .next RUN chown nextjs:nodejs .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing 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 # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output ENV HOSTNAME="0.0.0.0" CMD ["node", "server.js"]
解决方案
改用 npm 全局安装 pnpm,再使用 pnpm 安装依赖。修改后的 Dockerfile 部分代码如下:
RUN npm install -g pnpm && pnpm install --frozen-lockfile
这种方式成功避免了 Corepack 签名验证的问题,保证了依赖能够正确安装。
2. Runner 阶段缺少 pnpm 和 package.json
问题描述
在生产镜像运行时,先后出现了“Cannot find module ‘/app/pnpm’”和“ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND”错误,表明 runner 阶段缺少 pnpm 命令和 package.json 文件。
解决方案
在 runner 阶段显式全局安装 pnpm,并复制 package.json 至容器内:
FROM base AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs # 复制依赖、构建产物、public 目录以及 package.json COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./ # 在 runner 阶段全局安装 pnpm RUN npm install -g pnpm USER nextjs EXPOSE 8888 ENV PORT=8888 ENV HOSTNAME="0.0.0.0" CMD ["pnpm", "start"]
这样确保了启动时 pnpm 能够找到 package.json 和启动脚本,避免报错。
3. Next.js 构建输出问题:Standalone 模式 vs. 非 Standalone 模式
尝试方案 A:启用 Standalone 模式
原计划在 Next.js 构建中使用 Standalone 模式,通过在 next.config.cjs
中配置:
module.exports = { output: 'standalone', // 其他配置… }
以生成精简的构建产物,然后在 runner 阶段只复制 .next/standalone
和 .next/static
文件夹。但在实际尝试后,发现构建后 .next/standalone
并未生成,导致复制时报错:“/app/.next/standalone: not found”。经过排查,发现可能与 Next.js 版本、配置或项目结构有关,Standalone 模式方案最终无法奏效。
采用方案 B:非 Standalone 模式
最终选择复制整个 .next
目录,通过 Next.js 默认的启动命令启动应用。这种方式虽然生成的镜像体积较大,但更稳定,能确保构建产物完整。
修改后的 builder 和 runner 阶段代码如下:
# Builder 阶段:构建产物 FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm install -g pnpm RUN pnpm run build # Runner 阶段:生产环境镜像(非 Standalone 模式) FROM base AS runner WORKDIR /app RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./ RUN npm install -g pnpm USER nextjs EXPOSE 8888 ENV PORT=8888 ENV HOSTNAME="0.0.0.0" CMD ["pnpm", "start"]
确保 package.json 中有如下启动脚本:
"scripts": { "start": "next start", "build": "next build", // ... }
4. 环境变量加载问题
问题描述
启动时应用校验环境变量失败,日志中显示:
LEMONSQUEEZY_STORE_ID
被期望为数字,但解析为 NaN。LEMONSQUEEZY_WEBHOOK_SECRET
、LEMONSQUEEZY_API_KEY
、SUPABASE_KEY
等必填变量未提供有效值。- 同时,
LEMONSQUEEZY_SUBSCRIPTION_VARIANT_MAP
出现 “Invalid JSON” 错误(常见原因是外层引号问题)。
解决方案
使用 docker run
命令的 --env-file
参数加载环境变量文件,而不是依赖 Dockerfile 自动加载。假设你的 .env
文件内容如下(注意去掉数值型变量的引号,并保证 JSON 格式合法):
NEXT_PUBLIC_APP_URL="http://localhost.json4u.com:8888" NEXT_PUBLIC_FREE_QUOTA='{"graphModeView":30,"tableModeView":30,"textComparison":30,"jqExecutions":30}' NEXT_PUBLIC_SUPABASE_URL="https://kfuwzghygbtmonplcuou.supabase.co" NEXT_PUBLIC_SUPABASE_ANON_KEY="your_supabase_anon_key" NEXT_PUBLIC_HCAPTCHA_SITE_KEY="c87e1d8c-e81c-4dbc-a540-60246482751a" SUPABASE_PROJECT_ID="kfuwzghygbtmonplcuou" SUPABASE_KEY=your_supabase_key LEMONSQUEEZY_STORE_ID=1 LEMONSQUEEZY_SUBSCRIPTION_VARIANT_MAP={"monthly":1,"yearly":2} LEMONSQUEEZY_WEBHOOK_SECRET=your_webhook_secret LEMONSQUEEZY_API_KEY=your_api_key SENTRY_ORG="loggerhead" SENTRY_PROJECT="json4u" SENTRY_DSN="https://d60bd8847a6d8afc72e3de0d9288fa4c@o4506325094236160.ingest.us.sentry.io/4506325157085184" SENTRY_AUTH_TOKEN=your_sentry_auth_token
然后通过如下命令启动容器:
docker run -d -p 8888:8888 --env-file .env json4u:latest
这样 Docker 会自动将 .env 文件中的所有变量注入到容器中,确保应用在启动时能正确读取并验证这些环境变量。
5. 完整部署命令
构建镜像
在项目根目录下运行:
docker build -t json4u:latest .
运行容器并加载环境变量
确保 .env 文件在项目根目录,执行:
docker run -d -p 8888:8888 --env-file .env json4u:latest
总结
在 Docker 部署 json4u 项目的过程中,我遇到的主要问题和解决方案如下:
- 依赖安装问题:通过 npm 全局安装 pnpm,避免了 Corepack 的签名验证问题。
- Runner 阶段环境缺失:在生产阶段重新安装 pnpm,并复制 package.json,确保启动时能读取配置。
- Standalone 模式问题:尝试 Standalone 模式(方案 A)未能生成预期产物,最终采用复制整个
.next
目录的方案(方案 B),使用 Next.js 默认的启动命令。 - 环境变量加载问题:Dockerfile 不会自动加载主机上的 .env 文件,所以必须在 docker run 时使用
--env-file
参数来加载环境变量,并确保变量格式正确(如数值变量不加引号,JSON 格式保持合法)。
最终的部署指令如下:
构建镜像: docker build -t json4u:latest .
使用环境变量文件启动容器: docker run -d -p 8888:8888 --env-file .env json4u:latest
修改后的Dockerfile:
# build stage FROM node:20-alpine AS base # Install dependencies only when needed FROM base AS deps RUN apk add --no-cache libc6-compat WORKDIR /app COPY package.json pnpm-lock.yaml* ./ RUN npm install -g pnpm && pnpm install --frozen-lockfile # Builder 阶段:复制依赖和代码,并执行构建 FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN npm install -g pnpm RUN pnpm run build # Runner 阶段:生产环境镜像(非 standalone 模式) FROM base AS runner WORKDIR /app RUN npm install -g pnpm RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs # 复制 node_modules、.next 和 public 目录 COPY --from=builder /app/node_modules ./node_modules COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/package.json ./ USER nextjs EXPOSE 8888 ENV PORT=8888 ENV HOSTNAME="0.0.0.0" # 通过 package.json 中的 start 脚本启动服务 CMD ["pnpm", "start"]