EasyStarter logoEasyStarter

部署 Server

将 Hono 服务端部署到 Cloudflare Workers,包含 D1、R2 和 Secrets 配置

部署 Server(Cloudflare Workers)

EasyStarter 的服务端基于 Hono,运行在 Cloudflare Workers 上,通过 D1 作为数据库、R2 作为对象存储。

部署前请确认以下前置工作已完成:

  • Cloudflare 账号凭据已准备好(参见 Cloudflare 集成
  • D1 数据库已创建,已获取 Database ID(参见 数据库
  • R2 存储桶已创建,已获取 存储桶名称(参见 对象存储

EasyStarter 支持两种部署方式,按需选择:

方式适合场景
方式一:本地 CLI 部署快速上线、一次性部署、完全手动控制
方式二:GitHub 自动部署持续交付、团队协作、推送即部署

方式一:本地 CLI 部署

本地登录 Wrangler 后,手动执行部署命令。

npx wrangler login

环境变量说明

Server 端的变量分为三类,分别放在不同位置:

类型文件说明
公开配置apps/server/wrangler.jsoncvars非敏感值,明文写入配置,随代码部署
本地开发apps/server/.dev.vars本地 wrangler dev 自动加载,不参与部署
生产 Secretsapps/server/.env.production通过 wrangler secret bulk 加密推送到 Workers,不参与构建

不要.env.production 提交到 Git。.dev.vars 也应加入 .gitignore

更新 apps/server/wrangler.jsonc

将你的 Worker 名称、D1 Database ID、R2 存储桶名称和公开变量填入配置:

apps/server/wrangler.jsonc
{
  "name": "your-server-worker",          // Worker 名称,决定默认访问域名,全局唯一
  "main": "src/index.ts",
  "compatibility_date": "2025-06-15",
  "compatibility_flags": ["nodejs_compat"],
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "easystarter-db",
      "database_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  // D1 Database ID
    }
  ],
  "vars": {
    "NODE_ENV": "production",
    "WEBSITE_URL": "https://your-app.com",          // Web 端公开访问地址
    "SERVER_URL": "https://your-server.workers.dev", // Server Worker 自身地址
    "GITHUB_CLIENT_ID": "your-github-client-id",    // 公开值,无需保密
    "GOOGLE_CLIENT_ID": "your-google-client-id"     // 公开值,无需保密
  },
  "r2_buckets": [
    {
      "binding": "STORAGE",
      "bucket_name": "easystarter-bucket"           // R2 存储桶名称
    }
  ]
}
字段说明
nameWorker 名称,部署后默认 URL 为 https://<name>.<subdomain>.workers.dev
database_id创建 D1 数据库时获取的 UUID
vars.WEBSITE_URLWeb 应用地址,用于 Better Auth 的回调 URL、邮件跳转链接等
vars.SERVER_URLServer Worker 自身地址,用于 Better Auth 配置和 CORS
bucket_name与 Cloudflare 后台创建的 R2 存储桶名称保持一致

准备生产 Secrets(.env.production

创建 apps/server/.env.production,填入所有敏感变量。这个文件不参与构建,只用于下一步的 wrangler secret bulk 命令。

apps/server/.env.production
# Better Auth — 用于签名会话,必须是足够长的随机字符串
BETTER_AUTH_SECRET=

# GitHub OAuth — 只写 Secret,Client ID 已在 wrangler.jsonc vars 中
GITHUB_CLIENT_SECRET=

# Google OAuth — 只写 Secret,Client ID 已在 wrangler.jsonc vars 中
GOOGLE_CLIENT_SECRET=

# Resend 邮件服务
RESEND_API_KEY=

# R2 公开访问 URL(R2.dev 子域名或自定义域名)
R2_PUBLIC_URL=

# Stripe 支付
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# RevenueCat(仅启用移动端内购时填写)
REVENUECAT_SECRET_API_KEY=

注意事项:

  • BETTER_AUTH_SECRET 建议用 openssl rand -base64 32 生成,长度至少 32 位
  • GITHUB_CLIENT_SECRET / GOOGLE_CLIENT_SECRET 只需要 Secret,对应的 Client ID 已作为公开值放在 wrangler.jsoncvars
  • R2_PUBLIC_URL 是 R2 存储桶对外的访问地址,需要在 R2 后台开启 R2.dev subdomain 或绑定自定义域名后获取
  • 不使用某项集成(如 RevenueCat)时,对应变量可以留空或删除

部署 Worker

pnpm deploy:server

等价于在 apps/server 目录下执行 wrangler deploy,将源码编译后发布到 Cloudflare Workers。

首次部署成功后,控制台会输出 Worker 的访问地址:

Deployed your-server-worker triggers:
https://your-server-worker.your-subdomain.workers.dev

记录这个地址,后续配置 Web 端和更新 SERVER_URL 时需要用到。

推送 Secrets

.env.production 中的所有变量批量加密写入 Workers Secrets:

pnpm -F server secrets:bulk:production

等价于 wrangler secret bulk .env.production。推送后,变量以加密形式存储在 Cloudflare 侧,不会出现在部署代码或日志中。

Secrets 推送和代码部署是独立操作。每次更新敏感变量只需重新推送 Secrets,无需重新部署代码。

运行数据库迁移

将数据库 Schema 应用到 Cloudflare D1。

pnpm db:migrate 使用 drizzle-kit 的 D1 HTTP 驱动,需要以下三个变量在 apps/server/.dev.vars 中已填写:

apps/server/.dev.vars
CLOUDFLARE_ACCOUNT_ID=        # Cloudflare 账号 ID
CLOUDFLARE_API_TOKEN=         # 有 D1 Edit 权限的 API Token
CLOUDFLARE_D1_DATABASE_ID=    # D1 数据库 UUID

确认后执行:

pnpm db:migrate

迁移成功后,D1 中会创建所有必要的表(用户、会话、订阅、账单等)。

每次修改数据库 Schema 后,先执行 pnpm db:generate 生成迁移文件,再执行 pnpm db:migrate 应用到生产 D1。

验证部署

登录 Cloudflare Dashboard → Workers & Pages,选择刚部署的 Worker,在 Logs 标签下可以实时查看请求日志,确认服务正常响应。


方式二:GitHub 自动部署

将 GitHub 仓库与 Cloudflare 绑定后,每次推送到指定分支都会自动触发构建和部署,无需在本地执行任何命令。

连接 GitHub 仓库

  1. 进入 Cloudflare DashboardWorkers & Pages
  2. 点击 CreateWorkersConnect to Git
  3. 授权 Cloudflare 访问你的 GitHub 账户,选择对应仓库
  4. 选择部署分支(通常为 main

推送 Secrets

在连接仓库后、首次触发构建前,先通过本地 CLI 将所有 Secrets 推送到 Cloudflare,确保 Worker 启动时所有敏感变量已就绪:

pnpm -F server secrets:bulk:production

等价于 wrangler secret bulk .env.production,将 apps/server/.env.production 中的所有变量加密写入 Worker Secrets。

Secrets 推送和代码部署是独立操作。后续只有 Secrets 值变更时才需要重新推送,日常代码更新无需重推。

填写构建配置

在 Cloudflare 的构建设置页面填入以下配置:

项目
根目录/
构建命令pnpm --filter server build
部署命令pnpm --filter server run deploy
版本命令pnpm --filter server run deploy

根目录设置为 / 是因为这是 monorepo,pnpm workspace 需要从仓库根目录解析依赖。

运行数据库迁移

自动部署不会自动执行数据库迁移。首次部署完成后,仍需在本地手动运行:

pnpm db:migrate

确保 apps/server/.dev.vars 中已填写:

CLOUDFLARE_ACCOUNT_ID=
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_D1_DATABASE_ID=

后续每次修改 Schema 时,同样需要在本地执行 pnpm db:generate + pnpm db:migrate

配置完成后,每次向目标分支推送代码,Cloudflare 都会自动触发构建并部署最新版本。可在 Workers & Pages → 你的 Worker → Deployments 中查看每次部署的状态和日志。


自定义域名(推荐)

  1. 进入 Workers & Pages → 选择你的 Server Worker → Settings → Domains & Routes
  2. 点击 Add Custom Domain,输入已托管在 Cloudflare 的域名,例如 api.yourdomain.com
  3. 绑定成功后,按以下说明更新相关配置

为什么必须更新这两个字段

SERVER_URLWEBSITE_URL 不是普通环境变量——它们写在 wrangler.jsoncvars 块中,会在 wrangler deploy编译进 Worker Bundle,运行时直接读取。改了值之后必须重新部署才能生效。

这两个字段在认证系统中扮演关键角色:

字段用途
SERVER_URLBetter Auth 的 baseURL;OAuth 回调地址(/api/auth/callback/github 等);Cookie 的 domainsecure 策略
WEBSITE_URLBetter Auth 的 trustedOrigins(CORS 白名单);邮件中的跳转链接

如果这两个值与实际域名不匹配,OAuth 登录回调会 404,跨域请求会被 CORS 拦截,会话 Cookie 无法写入。

需要修改的文件

apps/server/wrangler.jsonc
"vars": {
  "WEBSITE_URL": "https://your-app.com",    // Web 端正式域名
  "SERVER_URL": "https://api.yourdomain.com" // Server 自定义域名(刚绑定的)
}

Web 端也持有 Server 的地址,用于前端直接调用 API:

apps/web/wrangler.jsonc
"vars": {
  "VITE_SERVER_URL": "https://api.yourdomain.com" // 与 SERVER_URL 保持一致
}

OAuth 应用后台

如果你绑定了新的 SERVER_URL,需要同步更新 OAuth 应用(GitHub / Google)的回调地址:

  • GitHub:Settings → Developer settings → OAuth Apps → 更新 Authorization callback URLhttps://api.yourdomain.com/api/auth/callback/github
  • Google:Google Cloud Console → 凭据 → OAuth 2.0 客户端 → 更新已授权的重定向 URI

重新部署使配置生效

同时部署 Server 和 Web(因为两边都有改动):

pnpm deploy:server
pnpm deploy:web

vars 是随代码打包的静态配置,不是 Secret。每次修改 wrangler.jsoncvars 都必须重新执行部署,仅推送 Secrets 不会更新这些值。