ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

GitDiagram 部署故障转移指南:基于 Dockerfile 与 railway.json 的离线 Railway 恢复方案

2026/9/14 22:13:48 拓冰建站 浏览量
GitDiagram 部署故障转移指南:基于 Dockerfile 与 railway.json 的离线 Railway 恢复方案 GitDiagram 部署故障转移指南基于 Dockerfile 与 railway.json 的离线 Railway 恢复方案【免费下载链接】gitdiagramFree, simple, fast interactive diagrams for any GitHub repository项目地址: https://gitcode.com/GitHub_Trending/gi/gitdiagramGitDiagram 是一套单一 Next.js 应用、单一线上部署目标的系统前端与全部后端 Route Handler 同进程运行日常流量由 Vercel 承载。当 Vercel 必须被临时替换时仓库内检入了Dockerfile与railway.json两份离线恢复资产可在 Railway 上重建同一套完整服务。本文以 docs/deployment-failover.md 为骨架结合仓库源码逐层拆解这套恢复方案的设计边界、完整操作步骤、健康检查机制与回切流程帮助读者在真实故障场景中安全、可验证地完成平台切换。一、方案定位离线恢复配方不是常驻备用先从定位说起。GitDiagram 只有一个应用实现和一个线上部署目标线上生产环境Live productionVercel 在gitdiagram.com上同时提供前端页面和每一个后端 Route Handler/api/generate/*、/api/healthz、/api/diagram-preview、/api/diagram-state、/api/browse-index等离线恢复选项Offline recovery optionDockerfile与railway.json可以把同一份应用打包部署到 Railway仅当 Vercel 日后必须被替换时才启用。需要明确的事实边界是当前没有已部署的 Railway 服务、没有已连接的 Railway 源、没有 Railway 域名、也没有 Railway DNS 记录。Railway 不在正常请求路径上不接收任何流量也不会产生任何常驻计算成本。这套恢复资产的意义在于随时可用、按需拉起而非双活容灾。从 docs/dev-setup.md 的部署章节也可以交叉印证The same source can be redeployed to Railway later throughDockerfileandrailway.json. Those files are an offline recovery recipe, not a live standby.同一份源码日后可通过Dockerfile与railway.json重新部署到 Railway这些文件是离线恢复配方而非实时备胎。两处文档描述一致恢复路径是项目部署策略中的一等公民。二、保留的恢复资产生产级 Docker 路径全貌该方案不引入任何第二套后端实现。文档明确强调This is not the old Python/FastAPI backend. No Python service or second API implementation is required.这不是旧的 Python/FastAPI 后端不需要 Python 服务或第二套 API 实现。恢复后暴露的 UI、Route Handler、图编译器graph compiler、配额逻辑、取消协议cancellation protocol与持久化代码与 Vercel 上运行的是完全相同的代码。2.1 Dockerfile三阶段构建 非 root 运行仓库根目录的 Dockerfile 采用经典的三阶段模式FROM oven/bun:1.3.14-alpine AS dependencies WORKDIR /app COPY package.json bun.lock ./ COPY patches ./patches RUN bun install --frozen-lockfile FROM oven/bun:1.3.14-alpine AS builder WORKDIR /app COPY --fromdependencies /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED1 ENV RAILWAY_DOCKER_BUILD1 RUN bun run build FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENVproduction ENV NEXT_TELEMETRY_DISABLED1 ENV HOSTNAME0.0.0.0 ENV PORT3000 RUN addgroup --system --gid 1001 nodejs \ adduser --system --uid 1001 nextjs \ mkdir .next \ chown nextjs:nodejs .next COPY --frombuilder --chownnextjs:nodejs /app/public ./public COPY --frombuilder --chownnextjs:nodejs /app/.next/standalone ./ COPY --frombuilder --chownnextjs:nodejs /app/.next/static ./.next/static USER nextjs EXPOSE 3000 CMD [node, server.js]逐层解读这份恢复路径对应的各项要求依赖层使用oven/bun:1.3.14-alpine执行bun install --frozen-lockfile与仓库 package.json 中packageManager: bun1.3.14和engines: { bun: 1.3.14 2 }的约束保持一致patches目录如 patches/minimatch3.1.5.patch会被完整复制保证patchedDependencies在构建中生效。构建层设置RAILWAY_DOCKER_BUILD1环境变量这是触发 Next.jsstandalone输出的开关见下文 2.3 节。bun run build对应 package.json 中的build: bun run --bun next build即用 Bun 作为构建运行时。运行层切换到node:22-alpine用node server.js直接运行 Next.js standalone 产物。HOSTNAME0.0.0.0让服务监听容器全部网卡PORT3000是默认值最终会被 Railway 注入的PORT覆盖文档要求respects the platform-providedPORT即尊重平台注入的端口。非 root 运行构建阶段创建nextjsuid/gid 1001系统用户COPY时用--chownnextjs:nodejs归属文件最后USER nextjs切换身份满足生产容器最小权限原则。2.2 railway.json平台侧的部署与健康检查契约仓库根目录的 railway.json 是 Railway 平台的部署配置文件{ $schema: https://railway.com/railway.schema.json, build: { builder: DOCKERFILE, dockerfilePath: Dockerfile }, deploy: { healthcheckPath: /api/healthz, healthcheckTimeout: 300, restartPolicyType: ON_FAILURE, restartPolicyMaxRetries: 5 } }build.builder DOCKERFILERailway 直接使用仓库内的Dockerfile构建镜像不需要额外的平台构建脚本deploy.healthcheckPath /api/healthz部署健康检查指向应用自身的健康检查路由见 2.4 节只有该路由返回成功Railway 才认为部署健康healthcheckTimeout 300健康检查超时上限为 300 秒给冷启动、依赖连接留足时间restartPolicyType ON_FAILURE与restartPolicyMaxRetries 5失败时自动重启最多重试 5 次。2.3 next.config.jsstandalone 输出的条件触发next.config.js 中的关键一行验证了 2.1 节的说法...(process.env.RAILWAY_DOCKER_BUILD 1 ? { output: standalone } : {}),也就是说output: standalone只在RAILWAY_DOCKER_BUILD1时启用——这正是 Dockerfile 构建阶段设置该变量的原因。Vercel 正常部署不受影响仍走 Vercel 自身的构建链路vercel.json 中声明bunVersion: 1.x让 Vercel 以 Bun 运行 Route Handler。这也解释了为什么这套恢复路径不需要浏览器 CORS 开关、不需要公开的后端选择器整个 Next.js 应用连同其standalone输出整体搬迁前端与 API 始终保持同源。2.4 /api/healthz部署健康检查与就绪探测Railway 的健康检查指向/api/healthz。它的实现位于 src/app/api/healthz/route.tsexport const runtime nodejs; export const dynamic force-dynamic; export async function GET() { const readiness await checkReadiness(); return NextResponse.json( { ok: readiness.ok, status: readiness.ok ? ok : unavailable, checks: readiness.checks }, { status: readiness.ok ? 200 : 503, headers: { Cache-Control: no-store }, }, ); }注意两点runtime nodejs与dynamic force-dynamic确保该路由走 Node 运行时且不被缓存返回体中的checks对象让排障者能一眼定位是哪个依赖出了问题。真正的探测逻辑在 src/server/readiness.ts 的checkReadiness()中包含五类检查检查项含义实现依据configuration必需的配置项是否齐全逐一readRequiredEnv校验R2_ACCOUNT_ID、R2_ACCESS_KEY_ID、R2_SECRET_ACCESS_KEY、R2_PUBLIC_BUCKET、R2_PRIVATE_BUCKET、UPSTASH_REDIS_REST_URL、UPSTASH_REDIS_REST_TOKEN、CACHE_KEY_SECRET定义见 src/server/storage/config.tsproviderAI 提供方密钥是否存在根据getProvider()判断需要OPENAI_API_KEY还是OPENROUTER_API_KEY对应AI_PROVIDERopenai/openrouterpublicStorage公开 R2 存储桶可访问checkR2Bucket()走一次与业务相同的认证 GetObject 路径src/server/storage/r2.tsprivateStorage私有 R2 存储桶可访问同上探测私有桶redisUpstash Redis 可连接checkUpstashConnection()发送PING并期望返回PONGsrc/server/storage/upstash.ts只有当Object.values(checks).every(Boolean)全部为真时ok才为true路由返回 200任何一个依赖不可用则返回 503。这与 src/app/api/healthz/route.test.ts 中的测试契约一致returns 200 only when required dependencies are ready、returns 503 when a required dependency is unavailable。因此/api/healthz不只是进程活着的探针而是依赖就绪度探针——这在恢复场景中极其重要它能防止 Railway 在 R2 或 Upstash 凭据未配置时就被错误地判定为部署成功。三、按需重建 Railway 的完整操作流程文档明确告诫Do not run these commands during normal operation.正常运行时不要执行这些命令。以下命令仅用于真实的恢复场景。整个过程分为七个步骤。第 1 步锁定精确提交并通过质量门禁# 检出与生产一致的精确提交并本地跑通质量门禁 bun run lint bun run typecheck bun run test bun run build对应 package.json 中的lint、typecheck、test、build脚本。之所以要求精确生产提交是因为恢复部署必须与 Vercel 上运行的应用行为完全一致任何未经过验证的提交都不应进入恢复路径。测试套件覆盖了确定性图编译器Mermaid 解析器契约测试、API 路由测试、取消与配额测试、存储并发测试以及浏览器渲染安全测试见 docs/dev-setup.md这些测试全部通过后再进入下一步。第 2 步关联或创建 Railway 项目# 关联当前目录到已存在的空 Railway 项目 railway link # 或新建一个名为 gitdiagram 的项目 railway init --name gitdiagram要求空项目是为了避免与旧环境残留混淆。第 3 步创建一个未连接unconnected的服务railway add --service gitdiagram-api这一步创建的是未连接 GitHub 源的服务。文档在第 5 步会再次强调这一点railway up不会把服务连接到 GitHub也不会自行创建公网域名——所有流量入口都是显式、临时的。第 4 步注入必需的环境变量密钥走 stdin从 .env.example 添加必需变量密钥值通过标准输入传入避免进入 shell 历史railway variable set VARIABLE_NAME --stdin --service gitdiagram-api恢复必需的核心变量分为四组组别变量说明R2 对象存储R2_ACCOUNT_ID、R2_ACCESS_KEY_ID、R2_SECRET_ACCESS_KEYS3 兼容客户端的端点和凭据见 src/server/storage/r2.ts 中https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com的拼接逻辑R2 存储桶R2_PUBLIC_BUCKET、R2_PRIVATE_BUCKET公开桶承载可公开访问的产物私有桶承载受限内容缓存与签名CACHE_KEY_SECRET用于缓存键的签名/哈希Upstash RedisUPSTASH_REDIS_REST_URL、UPSTASH_REDIS_REST_TOKEN共享配额、取消、分布式锁与故障状态的存储后端见 src/server/storage/upstash.ts同时还需配置 AI 提供方AI_PROVIDERopenai加OPENAI_API_KEY或AI_PROVIDERopenrouter加OPENROUTER_API_KEY否则readiness.ts中的provider检查将失败/api/healthz会返回 503。可选的生成控制项包括OPENAI_MODEL、OPENAI_COMPLIMENTARY_GATE_ENABLED、OPENAI_COMPLIMENTARY_DAILY_LIMIT_TOKENS、OPENAI_COMPLIMENTARY_MODEL_FAMILY、OPENROUTER_MODEL、OPENROUTER_SITE_URL、OPENROUTER_APP_NAME可选 GitHub 认证项包括GITHUB_PAT/GITHUB_PATS令牌池与 GitHub App 的GITHUB_APP_ID或GITHUB_CLIENT_ID、GITHUB_PRIVATE_KEY、GITHUB_INSTALLATION_ID浏览器分析可选NEXT_PUBLIC_POSTHOG_KEY。默认值示例AI_PROVIDERopenai、OPENAI_MODELgpt-5.6-terra可在 .env.example 中查到。第 5 步上传并部署当前检出railway up --service gitdiagram-api再次强调railway up既不连接 GitHub也不自行创建公网域名。部署过程实际执行 Dockerfile 的构建产物即 2.1 节描述的 standalone 应用。第 6 步添加临时域名并逐项验证添加一个临时 Railway 域名后按顺序验证以下五项核心能力健康检查/api/healthz返回 200 且checks全为true对应 src/server/readiness.ts 的五项就绪检查成本估算/api/generate/cost正常工作实现见 src/app/api/generate/cost/route.ts一次小规模流式生成/api/generate/stream能完整走通流式响应实现见 src/app/api/generate/stream/route.ts 与 src/features/diagram/sse.ts取消协议生成过程中的取消请求能被正确处理实现见 src/server/generate/cancellation.ts 与 src/app/api/generate/cancel/route.ts持久化的图状态/api/diagram-state能读写已持久化的图状态实现见 src/server/storage/diagram-state.ts。这五项恰好覆盖了恢复路径必须证明的完整闭环部署健康 → 计量正确 → 核心业务可用 → 生命周期控制可用 → 状态持久化可用。第 7 步仅在验证通过后做显式路由决策文档给出的原则是Keep Vercel intact until the incident is resolved.在事故解决前保持 Vercel 完好。只有上述五项全部通过才把流量显式切到临时域名对应的入口并且随时保留回切能力。为什么恢复不需要三样东西因为整个 Next.js 应用整体搬迁恢复过程不需要浏览器 CORS 开关前端与 API 同进程、同源部署不存在跨源问题公开的后端选择器不存在前端选后端的开关代码与 Vercel 完全一致数据迁移R2 拥有图产物diagram artifactsUpstash 拥有共享配额、取消、锁与故障状态——两者都是平台无关的外部服务随域名切换自动生效。这正是一个应用实现、一个部署目标、零数据搬迁方案的容灾优势。四、回切 Vercel 的流程事故解决后按四个步骤回切与恢复流程严格对称# 1. 验证 Vercel 侧健康 curl https://gitdiagram.com/api/healthz # 期望 200 # 并做一次小规模生产生成验证 # 2. 若路由被改动将 gitdiagram.com 恢复到预期的 Vercel 部署 # 3. 移除临时 Railway 域名删除 Railway 服务 railway domain --remove # 移除临时域名 railway delete --service gitdiagram-api # 删除恢复服务 # 4. 确认清理完成 # - Railway 项目下已无任何服务 # - DNS 区域中已无任何 Railway 记录回切完成后仓库内检入的恢复文件Dockerfile、railway.json仍然保留供下次事故使用而不需要保持 Railway 的计算资源、部署版本或公网端点常驻——这既降低了成本面也消除了无人维护的备用环境反而成为攻击面的风险。五、运维边界与安全检查要点综合文档与源码这套恢复方案有若干必须遵守的边界Railway 不参与正常请求路径没有已部署服务、没有连接源、没有域名、没有 DNS 记录因此不存在意外流量或重复扣费风险健康检查是就绪探针而非存活探针/api/healthz会真实探测 R2 双桶与 Upstash 连通性src/server/readiness.ts配置不全会返回 503healthcheckTimeout300 秒足够覆盖冷启动密钥不得进入 shell 历史一律使用railway variable set VARIABLE_NAME --stdin --service gitdiagram-api域名与流量是显式的railway up不建域名、不连 GitHub公网入口必须手动添加并同样手动移除回切以验证为前提Vercel 侧先过/api/healthz与一次生产生成再动路由最后清理 Railway 残留构建开关有明确的触发条件output: standalone仅在RAILWAY_DOCKER_BUILD1时生效next.config.js日常 Vercel 构建路径完全不受影响。六、总结GitDiagram 的部署故障转移方案可以用一句话概括一套代码、一份 Dockerfile、一个健康检查按需在 Railway 上重建与 Vercel 完全等价的生产服务验证通过后显式切流事故结束后完整回切并清理残留。这套方案没有引入第二套后端、没有常驻备用环境、没有数据迁移成本全部恢复能力都沉淀在 Dockerfile、railway.json 和/api/healthz就绪检查src/app/api/healthz/route.ts、src/server/readiness.ts中。对于采用单一 PaaS 部署的产品这套离线恢复配方的定位与实施步骤是一个值得复用的容灾设计样本。【免费下载链接】gitdiagramFree, simple, fast interactive diagrams for any GitHub repository项目地址: https://gitcode.com/GitHub_Trending/gi/gitdiagram创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考