关于 migrate-to-railcode
migrate-to-railcode 指导将可用应用移植到 Railcode,同时保留面向用户的行为并考虑平台差异。它帮助开发者盘点依赖、映射服务、分阶段迁移,并在切换用户前验证已部署应用。
主要功能
- 01盘点身份验证、数据和托管依赖
- 02将项目组件映射到 Railcode 服务
- 03指导分阶段移植应用
- 04为不支持的组件规划替代方案
- 05与原应用并行部署
- 06验证迁移行为并报告后续任务
适用场景
- 01评估应用是否适合 Railcode
- 02移植 Next.js 应用或容器化服务
- 03规划队列或 Webhook 的替代实现
- 04导入前核对数据所有权映射
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ railcode-hq/ railcode-skills --skill migrate-to-railcode安装后在 Claude Code 中输入/migrate-to-railcode确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/containers.mddatabases.mdnextjs-cache-adapter.tsnextjs-under-api.mdnextjs.md详细介绍
迁移到 Railcode
迁移是移植,不是重写:项目已经在某处运行,任务是在替换旧托管环境提供的功能(身份验证、数据库、环境变量、定时任务、文件存储)的同时,保留项目为用户提供的功能。
大多数内部工具都适合迁移。通常有一两个部分没有直接对应项,而其中几乎所有部分都能用其他方式重建,用户不会察觉——请参阅另寻他法。目前有一小部分内容完全无法托管——请参阅Railcode 目前无法托管的内容。做出任何承诺前,请先了解这两份清单。
按源项目分类的指南:
| 源项目 | 指南 | 状态 |
|---|---|---|
| Next.js(App Router) | references/nextjs.md | 支持 "type": "next",由 @railcode/next 构建。已在实际应用中验证,未覆盖的情况已在指南中列出 |
容器:通过 Dockerfile 或 docker-compose 堆栈运行任意语言编写的服务端 | references/containers.md | 方法和映射基于平台文档中的行为整理;尚无已记录的迁移案例作为依据 |
对于其他源项目,请遵循以下方法,并使用 create-railcode-app 构建应用。
Railcode 自带的 db 适用于快速、简单的用途。对于大量数据或复杂查询(不支持联接、聚合或事务;读取需分页),它有一定限制,因此需要这些功能的项目应保留原有数据库。references/databases.md介绍了这些限制、如何将数据库连接为数据源、如何通过密钥直接使用数据库、Railcode 尚无连接器支持的数据库、架构迁移的运行位置,以及如何通过 railcode agent-feedback 表达对托管 Postgres 的兴趣。
首先更新
运行 railcode 命令前,请更新技能和 CLI,并确认已发布的内容:
npx skills add Railcode-HQ/railcode-skills
npm install -g railcode@latest
railcode --version
npm view railcode version
如果无法连接 npm,请说明这一点,不要声称本指南是最新的。本版本依据 CLI 0.3.8、@railcode/sdk 0.4.0 和 @railcode/next 0.1.0 编写。
本技能不会重复介绍 SDK、清单或部署流程。这些内容位于 create-railcode-app 中(references/worker-sdk.md、references/cli-workflow.md、references/deployment.md、references/app-patterns.md);以下步骤提到 SDK 接口时,请阅读相应文档。
对于从其他托管环境迁入的人来说,Railcode 是什么
有四项事实会影响每次迁移。请先对照项目逐项确认。
- 每位查看者都是已登录该组织的成员。 没有匿名访客,也不支持自行注册。Railcode 托管的是内部工具。
- 后端是 Workers 运行时(
nodejs_compat)上的单个 worker 模块,不是 Node 服务端,也不是容器。它会针对每个请求运行一次,且不会在请求之间保留内存中的内容。不支持文件系统、子进程、原生扩展或原始 TCP。 - 只有在已登录成员的浏览器调用 worker,或应用自身的 cron 触发时,worker 才会运行。 公共互联网无法调用它。
- 身份验证由平台自动处理。 平台会在任何应用代码运行前为用户完成登录,并向 worker 提供经过验证的
ctx.user。项目自身的登录功能将不再需要,这通常是迁移中删除内容最多的一部分。
作为交换,应用无需设置即可获得:登录、存储(db)、文件存储(files)、LLM 网关(llm)、邮件发送、每个应用独立的密钥、定时任务、连接组织数据库和 SaaS 账号的连接器、托管 Agent、部署历史和日志。
方法
请按以下顺序操作。修改代码前先盘点:大多数迁移失败都是因为先移植了 UI,直到第二天才发现数据层没有对应功能。
1. 清点
阅读项目,并记录以下内容及其文件路径:
| 内容 | 查找位置 |
|---|---|
| 身份验证 | NextAuth/Auth.js、Clerk、Supabase auth、Passport、会话 Cookie、/login 路由 |
| 数据 | ORM(Prisma、Drizzle、SQLAlchemy、ActiveRecord)、数据库 URL、托管 KV 或 Blob 存储 |
| 密钥和配置 | .env*、process.env.*、Dockerfile 中的 ENV 行、旧主机的控制面板 |
| 定时和后台任务 | vercel.json 中的 cron、队列及其工作进程、服务器中的 setInterval、cron 容器 |
| 出站调用 | 服务器代码发起请求的所有主机 |
| 入站调用 | Webhook、公共 API 路由、OAuth 回调,以及所有非由应用自身前端调用的内容 |
| 文件 | 上传内容、生成的文档、S3 客户端、挂载卷 |
| 进程和二进制文件 | docker-compose 中的所有服务、所有 apt-get install 命令、代码通过 shell 调用的所有二进制文件 |
| 特定主机的软件包 | @vercel/*、平台 SDK、仅限边缘环境的 API |
2. 映射各项内容
| 项目现状 | 在 Railcode 上的对应方式 |
|---|---|
| 自有登录和会话 | 删除身份验证机制。读取 ctx.user(id、email、name、is_admin、roles)。appUsers 列出组织成员 |
| 用户表 | 保留属于应用数据的内容(个人资料、偏好、应用级角色),并迁移到 db。Railcode 用户 ID 不同于旧 ID——导入任何以用户为键的数据前,请参阅第 4 步 |
| 自有数据库中的逐用户记录 | 使用 db 集合——每个应用一个扁平存储,因此将用户 ID 放入键中(<userId>:<id>),并在服务器代码中强制执行访问控制。适用于少量、简单的数据;对于大型表或复杂查询,请保留数据库 |
| 应用读取的数据库(Postgres、BigQuery、Turso) | 将其连接为数据源:由组织管理员设置的数据连接器。只读。 优先使用已保存的查询(query(name, params));直接 SQL 使用 data(name).runSQL(...),并在清单中配置 adhoc_sql: |
| 应用读取并写入的数据库 | 保留并直接使用:将连接凭据存入 railcode secrets,在 egress: 下添加主机,并使用支持 HTTPS 的客户端(不支持原始 TCP)。或者将应用自己的表迁移到 db。参阅 references/databases.md |
| Railcode 没有连接器的数据库(MySQL、MongoDB、DynamoDB、Redis 等) | 同样,将凭据设为密钥,允许访问其主机,并像以前一样通过其 HTTPS API 使用 |
| 架构迁移(Prisma、Drizzle、Alembic、Rails) | 在构建或部署脚本中运行迁移,并在运行该脚本的机器上执行,绝不要在工作进程中运行。参阅 references/databases.md |
process.env.SECRET | 使用 railcode secrets set NAME 设置,并通过 secrets.NAME 读取 |
| Cron 任务 | 在 manifest.yaml 中配置 crons:,每个任务都使用 POST 调用应用自己的某个路由 |
| 出站请求 | 在 egress: 下列出各个主机 |
| 文件上传、挂载卷 | 使用 files |
| LLM 提供商密钥和客户端 | 使用 llm,并在清单中设置 llm: true |
| 事务性邮件 | 使用 email,并在清单中设置 email: true |
| 使用自有 OAuth 流程或 API 密钥调用的 SaaS API | 使用管理员或用户关联的连接器,并通过 connector(name) 调用;或者使用密钥并配置 egress: 主机 |
| 队列、WebSocket、Webhook 接收器、原生二进制文件、搜索索引 | 没有直接对应方案。请转到另寻方案 |
对于每一项没有直接对应方案的内容,移植前请告知用户:项目目前的行为、你提议的重建方案,以及对用户的影响(例如“新的付款会在一分钟内显示,而不是立即显示”)。逐项征得用户同意。用户同意的不同实现是良好的迁移;用户事后才发现的近似替代方案则是一个缺陷。
3. 迁移
- 添加 Railcode 文件(
railcode.json、manifest.yaml),并确保页面在railcode dev下渲染不变。 - 替换身份验证。
- 逐个替换数据层中的实体。
- 将密钥、定时任务和出站网络访问移入清单,并使用
railcode secrets。 - 重建另辟蹊径中的各个部分。
- 移除旧托管平台的包和配置。
确保每一步都能运行。如果迁移必须等到六步全部完成后才能运行,就无法调试。
railcode dev 会将 db 和 files 存储在本地临时存储中,但 SQL、连接器、电子邮件、
llm 和 Agent 会转发到真实实例。测试会发送电子邮件的路由时,确实会发送电子邮件。
4. 与原应用并行部署
部署到新的应用 slug,并让原应用继续运行。首次部署不会造成任何破坏,用户可以对比两个应用。只有在检查新应用后,才迁移数据并切换用户。
现有数据不会自动迁移。如果数据很重要,就编写一次性导入脚本(读取旧存储并通过临时的仅限管理员访问的路由写入,或通过 CLI 使用 railcode app kv),运行一次,然后删除脚本。
属于用户的记录需要转换所有者信息。旧应用的用户 ID 在 Railcode 中毫无意义,因此用旧 ID 导入记录会导致这些记录无法访问。先建立映射关系——旧 ID → 电子邮件地址 → Railcode 中具有该电子邮件地址的成员(appUsers)——告知用户哪些记录未能匹配,然后再导入,并将每条记录写入对应的 Railcode ID 下。
5. 验证后再报告
以真实用户身份打开已部署的应用,并测试每项已迁移功能;部署成功仅表示包已上传。railcode logs app --app <slug> 会列出每次调用,railcode logs app <invocation-id> 会显示其控制台输出和错误。
按以下顺序报告:哪些功能可用以及如何检查;哪些部分以不同方式重建以及用户体验有何变化;哪些内容未迁移以及原因;用户仍需执行哪些操作(设置密钥、关联连接器、导入数据)。
另辟蹊径
如果项目中的某个部分没有直接对应的实现方式,不要停下,也不要直接舍弃它。要问这部分要实现什么目的,而不是它由什么构成。队列的存在是为了让耗时的工作在用户无需等待的情况下完成。Webhook 的存在是为了让应用获知付款信息。WebSocket 的存在是为了让列表显示最新内容。这些目标通常都可以通过定时任务、存储和轮询的前端来实现。
可用的构建模块:
- 每分钟最多可触发一次的定时任务,可使用
db、files、llm、email、secrets、出站网络访问、SQL 和组织连接器。 - 可用作队列、缓存、锁或游标的存储。
- 可随意频繁调用工作进程的前端。 浏览器标签页没有子请求额度限制,而且会一直打开,因此可以逐步执行长时间任务。
- 托管 Agent,适用于耗时、需要执行代码或需要使用 AI 读取文件的工作。
- 用于连接组织现有数据库和 SaaS 账户的连接器。
- 可访问你声明的任意主机的出站 HTTP,让托管 API 能够完成本地二进制程序原本执行的工作。
适用的模式:
| 项目当前的做法 | 可以这样构建 | 对用户的影响 |
|---|---|---|
| 接收 Webhook,以获知发生了某件事(付款、新问题、表单提交) | 用 cron 轮询提供商的列表或事件 API。在 db 中将上次看到的 ID 或时间戳保存为游标,并确保处理过程具有幂等性 | 最多延迟一分钟 |
| 运行作业队列(BullMQ、Celery、Sidekiq) | 使用 jobs 集合。路由插入带有 status: "queued" 的行;cron 路由获取一小批作业并进行处理,然后将每行标记为已完成。cron 运行至少一次,且可能重叠,因此要用 ctx.invocationId 标记已领取的行,并确保副作用可以安全重复执行 | 作业会在一分钟内启动,而不是立即启动 |
| 在一次请求中运行一项长任务(大型导入、对数千行数据生成报告) | 将任务拆分为多个步骤,并在 db 中保存游标。前端循环调用步骤路由并显示进度,或者由 cron 推进任务。确保每个步骤远低于约 100 次子请求的限额 | 显示进度条,而不是转圈指示器 |
| 通过 WebSocket 或 SSE 推送实时更新 | 前端每隔几秒轮询一个开销较低的路由,且仅在标签页可见时轮询。返回一个 updated_at,这样没有变化的轮询只会产生很小的响应 | 更新会在轮询间隔内送达 |
| 向浏览器流式传输文本 | 返回流式 Response;SDK 中的 toNdjson() 负责处理分帧 | 无 |
| 在自己的数据库中联接和筛选数据 | 根据页面的读取方式设计键(<userId>:<id>、prefix()),存储少量重复数据,使用 query().where() 筛选,并持续分页,直到返回的页面较短。如果数据与其他系统共享,就将其保留在该数据库中,并通过数据连接器读取 | 如果键与页面匹配,则无影响 |
| 写入必须保持为关系型的数据库(需要事务、约束,或由其他系统读取) | 保留该数据库,并在工作器中通过密钥和 HTTPS 驱动程序直接使用(参考文档)。数据连接器为只读,因此只能用于读取。如果数据库没有 HTTPS 接口,就在前面加一层接口,或者在旧主机上保留一个小型写入服务 | 有 HTTPS 驱动程序时无影响 |
| 使用 Redis 或进程内存进行缓存 | 使用带有 expires_at 字段的 db 集合,并在读取时检查该字段。内存缓存无法跨请求保留 | 无 |
| 使用 Redis 进行速率限制或锁定 | 在 db 中为每个键存储一行,其中包含计数器和窗口起始时间。这不是原子操作,因此只能用于公平控制,不可用于资金处理 | 无 |
| 对自己的记录进行全文搜索 | 对于几千条记录,可在工作器或浏览器中加载并筛选。数据量更大时,通过连接器查询源数据库,或通过 egress: 调用托管搜索 API | 无 |
| 调用二进制文件(ffmpeg、ImageMagick、Chromium、pandoc) | 首先查找适用于工作器包的纯 JavaScript 库;PDF 和电子表格通常可以这样生成。否则,通过 egress: 调用托管 API,或将任务交给可以运行代码的托管 Agent | 结果可能较慢或为异步返回 |
| 自行运行针对 Google、Slack、GitHub 等服务的 OAuth 流程 | 使用连接器。管理员关联组织账户,或者每位用户关联自己的账户;工作器调用 connector(name),永远不会接触令牌 | 用户在 Railcode 中关联一次账户,而不是在应用中关联 |
| 接收邮件,或以真实个人或团队的地址发送邮件 | 使用该账户的 Gmail 连接器:组织所有的账户通过 cron 轮询并保存游标,个人账户则由个人托管 Agent 按计划读取其收件箱 | 收到邮件最多延迟一分钟 |
需要 llm 网关不支持的嵌入、视觉或其他模型功能 | 直接调用相应提供商:将密钥放在 secrets 中,将主机放在 egress: 中 | 无 |
| 发送推送或聊天通知 | 使用 email,或使用 Slack 连接器向频道发送消息 | 通知会通过电子邮件或 Slack 送达 |
| 多个内部页面中包含一个公开页面(公开表单、状态页) | 将其拆分。公开页面保留在旧主机上并写入其数据库;Railcode 应用通过数据连接器读取该数据库 | 需要两个部署,而不是一个 |
| 使用模板(Jinja、ERB、EJS)在服务器上渲染 HTML | 使用静态前端,从 /api/* 路由获取 JSON | 无 |
| 在 docker-compose 中运行多个服务 | 使用一个包含多个路由组的工作器。如果两个部分面向不同受众,就将其构建为两个应用 | 无 |
| 要做好这项工作,请遵循以下规则: |
- 说明会有哪些变化。 每一行都会有所取舍,通常是延迟。用一句话说明,并在开始构建前征得同意。
- 优先选择最简单的方案。 前端轮询胜过 cron;cron 胜过 Agent。
- 设计 cron 方案前先确认其限制:最短间隔为一分钟;每个应用最多五个计划任务;没有调用者(
ctx.user为null);并且无法启动或读取受管理的 Agent 运行。若五个计划任务不够,一个 cron 路由可以分派多种工作。 - 不要虚构保证。 如果原方案依赖事务、恰好一次投递,或第三方即时响应,那么轮询方案无法提供这些保证。相关内容见下一节。
如果用户需要的功能不在此表中,请用这些构建模块设计方案,告知用户这是一个新模式,并报告该模式(参见 反馈),以便将其加入。
Railcode 目前无法托管的内容
有些需求无法变通,因为它们需要互联网能够访问应用、持续运行的进程,或平台不提供的运行时。应在盘点阶段识别这些需求,而不是等到移植后才发现。
| 项目需求 | 不适用的原因 |
|---|---|
| 必须得到响应的入站 Webhook — Slack 斜杠命令和交互式按钮、Twilio 语音或 SMS 回复、会不断重试直到收到确认且没有可供轮询的 API 的服务提供商 | 组织外部的任何系统都无法调用该 Worker。仅用于通知的 Webhook 可以替换为轮询(见上文);要求应用作出响应的则不行 |
| 面向客户、合作伙伴、移动应用或其他服务器的公共 API 或公共端点 | 原因相同。请求来自已登录成员的浏览器或 Railcode 自有 Agent,而非外部系统 |
| 公共或面向客户的页面:营销网站、店面、自助注册、任何供搜索引擎收录的内容 | 所有访问者都必须是已登录的组织成员 |
| 自有身份系统:为非组织成员提供登录、向第三方签发自有 API 密钥或令牌 | 身份由组织成员资格决定 |
| 真正的实时功能:协同编辑、在线状态、多人游戏、WebSocket 服务端 | 没有推送接口。轮询能实现“看起来是最新的”,但无法做到“逐键实时更新” |
长时间运行的进程:守护进程、保持 Socket 连接的机器人、流式消费者(Kafka、Postgres LISTEN)、必须以短于一分钟的间隔触发的工作 | Worker 按请求运行,然后停止 |
非 HTTP 协议:通过 TCP 连接的数据库驱动(pg、mysql2、ioredis)、SMTP 服务端、gRPC | 不支持原始 TCP 入站或出站。数据库可通过数据连接器(读取)或带密钥的 HTTPS 驱动(读取和写入)访问;参见 references/databases.md。只有 Socket 接口且没有其他接入方式的数据库无法访问 |
| 容器本身:原样运行的 Python、Go、Ruby、Java 或 PHP 服务、系统包、原生扩展、GPU、本地模型 | 后端是一个 JavaScript 模块。其他语言需要移植,不能直接托管——参见容器指南 |
大型或多文件后端包:.wasm 模块、超出大小上限的 Worker(10 MB;不支持 worker_routes 的服务为 5 MB) | Worker 必须是单个 ESM 模块 |
| 自定义域名、原生移动应用、浏览器推送通知 | 应用是位于 <app>.<parent> 的 Web 应用 |
自有邮件域名:入站邮件服务端、应用拥有的邮箱地址,或通过自定义发件人调用 email.send() | email 只能通过平台发件人发送。支持通过个人拥有的 Gmail 帐户发送邮件;参见寻找其他方式 |
如果项目需要上述某项功能:
-
立即直截了当地告知用户。 说清楚该功能,并用一句话说明它目前无法在 Railcode 上运行及原因。不要把需要响应的功能模拟成轮询,并说成两者等效。
-
说明仍可实现的部分。 通常,项目其余部分可以迁移,只有这一部分留在原处,并共享一个由 Railcode 通过连接器读取的数据库。如果缺失的部分是项目的核心(例如公共店面、Slack 机器人),就说明项目目前应继续留在现有托管平台上。
-
使用 CLI 提交请求。 Railcode 团队会根据这些报告决定接下来要构建什么。每项不同的缺失能力都要提交一份请求,即使项目的部分内容已经迁移:
railcode agent-feedback --category suggestion \ --message "Migration blocked on inbound webhooks. The project receives POST /webhooks/slack for slash commands and must reply within 3 seconds; there is no API to poll instead. Request: a way for an app to expose a public, signature-verified endpoint." \ --context "Source: Express + Postgres in a container, about 40 routes. Everything else mapped to db, secrets and crons. The Slack routes stayed on the old host; the rest was migrated." \ --agent "<your assistant name>" --model "<your model>"报告要写得让从未接触过该项目的人也能理解需求:项目是什么(技术栈和大致规模)、它具体需要什么、你改用什么方案,以及迁移是否继续进行。不要包含凭据、客户数据、源代码或任何可识别用户客户的信息。该命令需要已有登录,并会输出
Feedback accepted.;所有选项都在create-railcode-app的 “Report Railcode feedback” 部分。如果命令失败,告知用户报告未发送,然后继续处理。 -
告知用户你已发送报告,并说明报告内容。
如果平台已有其他实现方式,不要提交请求;请先查看寻找其他实现方式和 create-railcode-app。
机器安全
框架构建很耗资源,而迁移会运行许多次构建。
- 一次只运行一个构建或开发服务器,并在结束本轮前停止你启动的所有进程。使用
ps检查,不要想当然。 - 绝不要将
package.json的build脚本指向会调用框架适配器的程序,前提是该适配器本身会运行npm run build。这会导致构建链无限延伸。Next.js 指南中有具体规定。 - 除非用户要求,否则不要启动项目的 docker-compose 堆栈来了解其工作方式。请阅读代码。
反馈
每次迁移后,都使用 railcode agent-feedback 报告具体阻碍(用法见上文和 create-railcode-app),每项发现提交一份报告:源项目使用了什么、什么功能没有等效实现、你采用了什么重建模式,以及指南未能预见的故障。缺失的能力使用 --category suggestion;本技能中错误或不清晰的步骤使用 --category friction。这就是下一份指南的编写依据。