部署说明
面向 TurboRepo 的可复制部署手册(Vercel / Cloudflare)。
部署模型
Bunship 是 TurboRepo monorepo。线上只部署一个站点(apps/ship),同时承载:
- 营销页
- 业务页
- 管理后台
- 文档路由与文档搜索接口
/api/v1/*API 路由
无需单独部署 docs 项目。
1. 前置环境变量(先复制)
先生成密钥:
openssl rand -base64 32生产环境最小可用变量(可直接粘贴再替换):
# 认证与站点地址
BETTER_AUTH_SECRET="replace-with-long-random-secret"
AUTH_SECRET="replace-with-long-random-secret"
SITE_URL="https://你的域名"
TRUSTED_ORIGINS="https://你的域名"
ADMIN_EMAIL_LIST="admin@你的域名"
EMAIL_FROM="Bunship <noreply@你的域名>"
# OAuth
OAUTH_GITHUB_CLIENT_ID="xxx"
OAUTH_GITHUB_CLIENT_SECRET="xxx"
OAUTH_GOOGLE_CLIENT_ID="xxx"
OAUTH_GOOGLE_CLIENT_SECRET="xxx"
PUBLIC_OAUTH_GOOGLE_CLIENT_ID="xxx"
# 数据库与邮件
DATABASE_URL="postgresql://..."
RESEND_API_KEY="re_xxx"
# 支付(Stripe / Creem)
STRIPE_SECRET_KEY="sk_live_xxx"
STRIPE_WEBHOOK_SECRET="whsec_xxx"
CREEM_API_KEY="creem_xxx"
CREEM_WEBHOOK_SECRET="creem_whsec_xxx"
PAYMENT_PROVIDER_DEFAULT="stripe"
# 存储(S3 / R2)
S3_ENDPOINT="https://<account>.r2.cloudflarestorage.com"
S3_REGION="auto"
S3_ACCESS_KEY="xxx"
S3_SECRET_KEY="xxx"
S3_BUCKET="your-bucket"
PUBLIC_S3_URL_BASE="https://cdn.你的域名"
# 当前模板校验还会要求
S_GITHUB_PERSONAL_ACCESS_TOKEN="ghp_xxx"
CLOUDFLARE_ACCOUNT_ID="xxx"若使用 Better Auth,请设置 AUTH_SECRET 与 BETTER_AUTH_SECRET(可复用同一个强随机值)。若使用 Clerk,请配置 PUBLIC_CLERK_PUBLISHABLE_KEY、CLERK_SECRET_KEY 和 CLERK_WEBHOOK_SECRET。
/api/v1 现在是固定的同源路由前缀,不再需要单独的公开 API 地址变量。只有当你刻意把 auth/API 暴露到另一个域名时,才需要显式设置 BETTER_AUTH_URL,并把额外域名加入 TRUSTED_ORIGINS。
PUBLIC_AUTH_PROVIDER 仍然是构建期变量,因为它会选择 auth provider 打包分支。其他 public 值建议在运行时使用 PUBLIC_*;旧的 NEXT_PUBLIC_* 名称仍作为 fallback 兼容。
相关配置文档:
- Better Auth: https://www.better-auth.com/docs/introduction
- Clerk: https://clerk.com/docs
- GitHub OAuth App: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
- Google OAuth: https://developers.google.com/identity/protocols/oauth2
- Stripe Key/Webhook: https://docs.stripe.com/keys、https://docs.stripe.com/webhooks
- Resend: https://resend.com/docs
- 仓库完整变量清单:/zh/docs/operations/env-config
2. Vercel 部署(TurboRepo)
由于这是 TurboRepo,有两种可用配置。
方案 A(推荐):Turbo Root 模式
把 Vercel 的 Root Directory 设为仓库根目录。
| Vercel 配置项 | 值 |
|---|---|
| Framework Preset | Next.js |
| Root Directory | .(仓库根目录) |
| Install Command | bun install |
| Build Command | bunx turbo run build --filter=@bunship-ai/ship... |
| Output Directory | 留空(Next.js 默认) |
推荐原因:更符合 monorepo 依赖图,Turbo 缓存和依赖构建更稳定。
方案 B:App Root 模式
如果继续用 Root Directory = apps/ship,则:
| Vercel 配置项 | 值 |
|---|---|
| Framework Preset | Next.js |
| Root Directory | apps/ship |
| Include files outside root directory in Build Step | Enabled |
| Install Command | bun install |
| Build Command | bun run build |
| Output Directory | 留空(Next.js 默认) |
App Root 模式下,务必保持 “Include files outside root directory in Build Step = Enabled”,否则工作区包可能无法解析。
部署完成后配置支付 Webhook:
- Stripe:
https://你的域名/api/v1/webhook/stripe - Creem:
https://你的域名/api/v1/webhook/creem
参考链接:
- Vercel Monorepo: https://vercel.com/docs/monorepos
- Vercel Build 设置: https://vercel.com/docs/builds/configure-a-build
- Turbo Filter: https://turbo.build/repo/docs/crafting-your-repository/running-tasks#using-filters
3. Cloudflare 部署(OpenNext)
仓库已内置 OpenNext Cloudflare 配置(apps/ship/open-next.config.ts、apps/ship/wrangler.jsonc)。
从仓库根目录执行:
bun install
cd apps/ship
bunx wrangler login
bun run deploybun run deploy 会执行:
opennextjs-cloudflare buildopennextjs-cloudflare deploy
参考链接:
- OpenNext Cloudflare: https://opennext.js.org/cloudflare
- Wrangler 配置: https://developers.cloudflare.com/workers/wrangler/configuration/
- Secrets 与变量: https://developers.cloudflare.com/workers/configuration/secrets/
Cloudflare 运行时建议显式设置 VERCEL_ENV=production,与模板内部生产分支行为保持一致。
4. GHCR Docker 镜像
仓库已经包含发布私有 Docker 镜像到 GitHub Container Registry 的 GitHub Actions 工作流:
- 工作流:
.github/workflows/publish-ghcr.yml - 镜像地址:
ghcr.io/<owner>/<repo> - 构建文件:
Dockerfile - 触发方式:推送到
main且改动 app/package/build 文件,或手动workflow_dispatch
工作流会构建两种 auth provider 镜像。默认镜像 tag 使用 Better Auth;Clerk 使用显式 Clerk tag:
| Auth provider | Tags |
|---|---|
| Better Auth | 默认分支上的 latest、分支 tag、<branch>-<sha> |
| Clerk | 默认分支上的 clerk-latest、clerk-<sha> |
GHCR Docker build 默认不需要业务运行时 secrets。镜像构建使用 build args,并可通过 BuildKit secret file 读取 Infisical 中的最小构建输入;业务 env 在容器启动时由部署平台注入。
GitHub Actions variables(GHCR build / Infisical OIDC):
| 变量 | 用途 |
|---|---|
INFISICAL_IDENTITY_ID | 可选,Infisical OIDC identity;入口见环境变量文档 |
INFISICAL_PROJECT_SLUG | 可选,Infisical project slug |
INFISICAL_ENV_SLUG | 可选,Infisical environment slug |
INFISICAL_DOMAIN | 可选,默认 https://app.infisical.com |
INFISICAL_SECRET_PATH | 可选,Infisical secret folder path,默认 /;如果 folder 是根目录下的 env,填 /env |
INFISICAL_RECURSIVE | 可选,默认 false;只有 Docker build 输入放在子目录里才填 true |
INFISICAL_INCLUDE_IMPORTS | 可选,默认 true;需要读取 Infisical imports 时保留默认 |
VERCEL_ENV | 可选,默认 production |
GHCR 构建 workflow 会保留 Load Infisical secrets,并把结果写成 .infisical.docker.env 后作为 infisical_env BuildKit secret 传入 Dockerfile。这份文件只在 build step 中读取,不会复制进镜像层。这里的 Infisical 只用于拿 Docker build 需要的最小输入,不代表 SITE_URL、DATABASE_URL、S3_*、OAuth、Stripe、Email 等业务运行时变量属于 Docker build。
Infisical OIDC 配置要点:publish-ghcr.yml 已有 permissions.id-token: write,所以 OIDC auth method not found for identity 一般不是 GitHub 没发 token,而是 INFISICAL_IDENTITY_ID 对应的 machine identity 仍是 Universal Auth。到 Infisical > Project > Access Control > Machine Identities 打开该 identity,在 Authentication 里移除 Universal Auth 并添加 OIDC Auth;OIDC Discovery URL 和 Issuer 都填 https://token.actions.githubusercontent.com。因为当前 workflow 使用 GitHub Environment,生产 subject 建议填 repo:<owner>/<repo>:environment:Production,dev 分支填 repo:<owner>/<repo>:environment:staging。
Docker build args:
| 变量 | 用途 |
|---|---|
GIT_COMMIT_SHA | workflow 自动传,用于镜像版本/前端版本展示 |
VERCEL_ENV | 默认 production |
PUBLIC_AUTH_PROVIDER | workflow matrix 自动传,生成 Better Auth / Clerk 两类镜像 |
不要把 SITE_URL、DATABASE_URL、S3_*、auth secrets、Stripe keys、email keys、OAuth client ID/secret 放进 Docker build。它们属于容器运行时 env。PUBLIC_S3_URL_BASE、PUBLIC_CLERK_PUBLISHABLE_KEY、PUBLIC_OAUTH_GOOGLE_CLIENT_ID、PUBLIC_GA_ID、PUBLIC_UMAMI_DATA_ID 和 PUBLIC_SERVER_URL 也都是运行时 public config,因此同一个 GHCR 镜像可以在不同环境复用。旧的 NEXT_PUBLIC_* 名称仍作为 fallback 兼容,但新部署不建议再使用。不要再设置 NEXT_PUBLIC_SITE_URL、NEXT_PUBLIC_API_PREFIX、API_PREFIX 或 CORS_ORIGIN。
只有浏览器访问的 API origin 与 SITE_URL 不一致时才设置 PUBLIC_SERVER_URL。有 VERCEL_ENV 时请求 ${PUBLIC_SERVER_URL}/api/v1;没有 VERCEL_ENV 时请求 ${PUBLIC_SERVER_URL}/v1。
5. 一键部署清单(照单执行)
Vercel
- 导入仓库
- 按上文选择方案 A 或 B 填写构建设置
- 填入前置环境变量
- 点击 Deploy
- 配置 Stripe Webhook
- 执行下方冒烟检查
Cloudflare
- 创建/选择 Workers 项目
- 填入环境变量与 Secrets
- 执行
bun install - 执行
cd apps/ship && bun run deploy - 绑定自定义域名
- 执行下方冒烟检查
GHCR
- 配置 Infisical OIDC variables(如果 Docker build 最小变量走 Infisical),或保留 workflow 默认 build args
- 确认 Infisical machine identity 的 Authentication 是 OIDC Auth,不是默认 Universal Auth
- 按 GitHub Environment 配置 OIDC Subject:
main使用repo:<owner>/<repo>:environment:Production,dev使用repo:<owner>/<repo>:environment:staging - 确认镜像构建不依赖业务 runtime secrets
- 确认
SITE_URL只在容器运行时可用 - 在容器运行时设置
PUBLIC_S3_URL_BASE、PUBLIC_CLERK_PUBLISHABLE_KEY、PUBLIC_OAUTH_GOOGLE_CLIENT_ID等 public env - 仅当公开 API origin 与
SITE_URL不一致时在运行时设置PUBLIC_SERVER_URL,并确认有VERCEL_ENV时该 origin 暴露/api/v1,没有VERCEL_ENV时暴露/v1 - 推送到
main或手动运行 workflow - Better Auth 拉取
ghcr.io/<owner>/<repo>:latest、分支 tag 或带分支前缀的 sha tag;Clerk 拉取clerk-latest或clerk-<sha> - 给容器注入运行时 env
- 执行下方冒烟检查
6. 上线后冒烟验证
-
/:locale/docs -
/:locale/signin -
/:locale/subscription -
/:locale/admin -
/api/v1/health(如你当前环境已开放) -
/api/docs/search