BunshipBunship
快速开始

部署说明

面向 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_SECRETBETTER_AUTH_SECRET(可复用同一个强随机值)。若使用 Clerk,请配置 PUBLIC_CLERK_PUBLISHABLE_KEYCLERK_SECRET_KEYCLERK_WEBHOOK_SECRET

/api/v1 现在是固定的同源路由前缀,不再需要单独的公开 API 地址变量。只有当你刻意把 auth/API 暴露到另一个域名时,才需要显式设置 BETTER_AUTH_URL,并把额外域名加入 TRUSTED_ORIGINS

PUBLIC_AUTH_PROVIDER 仍然是构建期变量,因为它会选择 auth provider 打包分支。其他 public 值建议在运行时使用 PUBLIC_*;旧的 NEXT_PUBLIC_* 名称仍作为 fallback 兼容。

相关配置文档:

2. Vercel 部署(TurboRepo)

由于这是 TurboRepo,有两种可用配置。

方案 A(推荐):Turbo Root 模式

把 Vercel 的 Root Directory 设为仓库根目录。

Vercel 配置项
Framework PresetNext.js
Root Directory.(仓库根目录)
Install Commandbun install
Build Commandbunx turbo run build --filter=@bunship-ai/ship...
Output Directory留空(Next.js 默认)

推荐原因:更符合 monorepo 依赖图,Turbo 缓存和依赖构建更稳定。

方案 B:App Root 模式

如果继续用 Root Directory = apps/ship,则:

Vercel 配置项
Framework PresetNext.js
Root Directoryapps/ship
Include files outside root directory in Build StepEnabled
Install Commandbun install
Build Commandbun 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

参考链接:

3. Cloudflare 部署(OpenNext)

仓库已内置 OpenNext Cloudflare 配置(apps/ship/open-next.config.tsapps/ship/wrangler.jsonc)。

从仓库根目录执行:

bun install
cd apps/ship
bunx wrangler login
bun run deploy

bun run deploy 会执行:

  1. opennextjs-cloudflare build
  2. opennextjs-cloudflare deploy

参考链接:

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 providerTags
Better Auth默认分支上的 latest、分支 tag、<branch>-<sha>
Clerk默认分支上的 clerk-latestclerk-<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_URLDATABASE_URLS3_*、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:Productiondev 分支填 repo:<owner>/<repo>:environment:staging

Docker build args:

变量用途
GIT_COMMIT_SHAworkflow 自动传,用于镜像版本/前端版本展示
VERCEL_ENV默认 production
PUBLIC_AUTH_PROVIDERworkflow matrix 自动传,生成 Better Auth / Clerk 两类镜像

不要把 SITE_URLDATABASE_URLS3_*、auth secrets、Stripe keys、email keys、OAuth client ID/secret 放进 Docker build。它们属于容器运行时 env。PUBLIC_S3_URL_BASEPUBLIC_CLERK_PUBLISHABLE_KEYPUBLIC_OAUTH_GOOGLE_CLIENT_IDPUBLIC_GA_IDPUBLIC_UMAMI_DATA_IDPUBLIC_SERVER_URL 也都是运行时 public config,因此同一个 GHCR 镜像可以在不同环境复用。旧的 NEXT_PUBLIC_* 名称仍作为 fallback 兼容,但新部署不建议再使用。不要再设置 NEXT_PUBLIC_SITE_URLNEXT_PUBLIC_API_PREFIXAPI_PREFIXCORS_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:Productiondev 使用 repo:<owner>/<repo>:environment:staging
  • 确认镜像构建不依赖业务 runtime secrets
  • 确认 SITE_URL 只在容器运行时可用
  • 在容器运行时设置 PUBLIC_S3_URL_BASEPUBLIC_CLERK_PUBLISHABLE_KEYPUBLIC_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-latestclerk-<sha>
  • 给容器注入运行时 env
  • 执行下方冒烟检查

6. 上线后冒烟验证

  • /:locale/docs
  • /:locale/signin
  • /:locale/subscription
  • /:locale/admin
  • /api/v1/health(如你当前环境已开放)
  • /api/docs/search