环境变量与配置
Bunship Web + API 运行所需的环境变量清单。
本页是当前仓库 必需环境变量 的权威清单。
运行时 public config 现在优先使用 PUBLIC_* 命名。旧的
NEXT_PUBLIC_* 仍作为 fallback 兼容;PUBLIC_AUTH_PROVIDER
是构建期变量,用来选择 auth provider 构建分支。
代码参考:
- web schema:
apps/ship/src/env.ts - auth runtime:
packages/auth/src/server.ts,packages/auth-clerk/src/server.ts - api runtime:
apps/ship-api/src/server.ts与apps/ship-api/src/index.ts - Trigger runtime sync:
apps/ship-api/trigger.config.ts - Docker build:
Dockerfile,.github/workflows/publish-ghcr.yml
环境归属矩阵
先按“谁读取这个变量”归类,不要把 build、容器 runtime、Trigger runtime 混在一起。
| 场景 | 变量归属 | 是否存业务 secret | 说明 |
|---|---|---|---|
| Docker build / GHCR publish | GIT_COMMIT_SHA, VERCEL_ENV, PUBLIC_AUTH_PROVIDER,以及可选 BuildKit secret file infisical_env | 否 | 只决定镜像版本和 auth provider 构建分支。Dockerfile 已设置 SKIP_ENV_VALIDATION=1,默认不需要业务 runtime env。 |
| GHCR Docker build 的 Infisical 控制变量 | INFISICAL_IDENTITY_ID, INFISICAL_PROJECT_SLUG, INFISICAL_ENV_SLUG, INFISICAL_DOMAIN, INFISICAL_SECRET_PATH, INFISICAL_RECURSIVE, INFISICAL_INCLUDE_IMPORTS | 否 | 只用于 publish-ghcr.yml 通过 OIDC 找到 Infisical 项目/环境/folder,把结果写成 .infisical.docker.env 并作为 Docker BuildKit secret 传入。 |
| Trigger deploy CI | TRIGGER_ACCESS_TOKEN, TRIGGER_PROJECT_ID | TRIGGER_ACCESS_TOKEN 是 secret | 只用于 deploy-trigger.yml 部署任务;不会作为 Trigger 任务运行时变量同步。 |
| Web/API 容器 runtime | SITE_URL, DATABASE_URL, auth、email、payment、storage、OAuth、public runtime config 等 | 是 | 容器启动后由部署平台注入。OAuth client ID/secret 只属于这里。 |
| Web/API Trigger dispatch | TRIGGER_SECRET_KEY, 可选 TRIGGER_PROJECT_ID | 是 | 让 Web/API 选择 Trigger adapter 并向 Trigger.dev 投递任务。不是 Docker build 变量。 |
| Trigger 云端 task runtime | DATABASE_URL, S3_*, PUBLIC_S3_URL_BASE, provider API keys, AI tuning vars | 是 | 任务跑在 Trigger.dev 云端,需要单独配置/同步;不需要 OAuth client ID。 |
| BullMQ runtime | REDIS_URL | 是 | 自建持久 worker 队列使用;和 Trigger runtime 二选一。 |
最小必需(Web + API)
缺失这些变量会导致启动失败,或影响认证/支付/存储等核心流程。
| 变量 | 是否必需 | 使用方 | 说明 |
|---|---|---|---|
PUBLIC_AUTH_PROVIDER | 是(默认 better-auth) | web, api | better-auth 或 clerk |
DATABASE_URL | 是 | web, api, auth | Postgres 连接串 |
SITE_URL | 是 | web, api, auth | 站点主域名,用于 SEO、邮件、认证链接和绝对地址 |
TRUSTED_ORIGINS | 建议 | api, auth | Better Auth 与 CORS 使用的额外可信来源(逗号分隔) |
PUBLIC_SERVER_URL | 可选 | web | 浏览器请求的运行时 API origin 覆盖项;仅当公开 API origin 与 SITE_URL 不一致时设置;有 VERCEL_ENV 时目标必须暴露 /api/v1,否则必须暴露 /v1。旧变量 NEXT_PUBLIC_SERVER_URL 仍兼容。 |
ADMIN_EMAIL_LIST | 是 | web, auth | 管理员邮箱列表(逗号分隔) |
EMAIL_FROM | 是 | auth | 邮件发件人(验证码/重置密码/OTP) |
RESEND_API_KEY | 是 | web/auth | 邮件服务密钥 |
S3_ENDPOINT | 是 | web, api | 存储服务地址 |
S3_REGION | 是 | web, api | 存储区域 |
S3_ACCESS_KEY | 是 | web, api | 存储访问密钥 |
S3_SECRET_KEY | 是 | web, api | 存储访问密钥密文 |
S3_BUCKET | 是 | web, api | Bucket 名称 |
PUBLIC_S3_URL_BASE | 是 | web, api | 公网对象地址前缀。旧变量 NEXT_PUBLIC_S3_URL_BASE 仍兼容。 |
STRIPE_SECRET_KEY | 是 | web, api | Stripe API Key |
STRIPE_WEBHOOK_SECRET | 是 | web, api | Stripe Webhook 签名密钥 |
S_GITHUB_PERSONAL_ACCESS_TOKEN | 是 | web, api | 管理端/功能页用的 GitHub API Token |
CLOUDFLARE_ACCOUNT_ID | 是(web schema) | web | 当前环境校验要求 |
上传 Provider 选择
上面的 S3_* 是基础存储配置。Better Upload 服务端路由还可以从 settings 动态选择 provider,再回退到环境变量。这是服务端配置机制,不是用户自助切换 provider 的界面。
Provider 解析顺序:
settings表里的UPLOAD_PROVIDER_OVERRIDES[userId]settings表里的UPLOAD_PROVIDER_DEFAULTBETTER_UPLOAD_PROVIDERcloudflare
| 变量 | 是否必需 | 使用方 | 说明 |
|---|---|---|---|
BETTER_UPLOAD_PROVIDER | 否(默认 cloudflare) | api | 没有 settings 覆盖时的 env 兜底 provider。支持值:cloudflare、aws、backblaze、tigris、digitalocean、minio、wasabi、custom。无效值会被忽略并回退到 cloudflare。 |
BETTER_UPLOAD_<PROVIDER>_BUCKET | 否 | api | provider 专属 bucket 兜底,例如 BETTER_UPLOAD_AWS_BUCKET 或 BETTER_UPLOAD_CUSTOM_BUCKET。 |
BETTER_UPLOAD_BUCKET | 否 | api | 通用 bucket 兜底,优先级高于 S3_BUCKET。 |
凭证和客户端字段优先从 settings 的 client 对象读取,再回退到 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY、S3_ACCESS_KEY / S3_SECRET_KEY 等环境变量别名。Path-style 或 provider 专属 client 行为请写在 settings 的 client 对象里,不再作为全局环境变量配置。
按用户切 provider 的 settings 示例和 bucket 解析细节,请看S3 协议存储。
Better Auth 专用(当 PUBLIC_AUTH_PROVIDER=better-auth)
| 变量 | 是否必需 | 使用方 | 说明 |
|---|---|---|---|
BETTER_AUTH_SECRET | 是 | auth/api | Better Auth 签名密钥 |
BETTER_AUTH_URL | 否(默认 SITE_URL) | auth/api | 当 auth/API 对外域名与站点域名不同时,用于覆盖公开地址 |
AUTH_SECRET | 是 | web/auth | Web 环境校验必需 |
OAUTH_GITHUB_CLIENT_ID | 是(开启 GitHub OAuth 时) | web/auth | GitHub OAuth |
OAUTH_GITHUB_CLIENT_SECRET | 是(开启 GitHub OAuth 时) | web/auth | GitHub OAuth |
OAUTH_GOOGLE_CLIENT_ID | 建议 | auth | Google OAuth 服务端 Client ID |
OAUTH_GOOGLE_CLIENT_SECRET | 建议 | auth | Google OAuth 服务端 Secret |
PUBLIC_OAUTH_GOOGLE_CLIENT_ID | 是(开启 Google OAuth 时) | web | Google One Tap 与 Google 登录按钮需要。旧变量 NEXT_PUBLIC_OAUTH_GOOGLE_CLIENT_ID 仍兼容。 |
支付提供商扩展
| 变量 | 是否必需 | 使用方 | 说明 |
|---|---|---|---|
STRIPE_SECRET_KEY | 是(使用 Stripe 时) | web, api | Stripe 密钥 |
STRIPE_WEBHOOK_SECRET | 是(使用 Stripe 时) | web, api | Stripe Webhook 签名密钥 |
CREEM_API_KEY | 条件 | web, api | Creem API Key(使用 Creem 时必填) |
CREEM_WEBHOOK_SECRET | 条件 | web, api | Creem Webhook 签名密钥 |
PAYMENT_PROVIDER_DEFAULT | 否(默认 stripe) | api | 无激活配置时的默认支付提供商 |
Clerk 专用(当 PUBLIC_AUTH_PROVIDER=clerk)
| 变量 | 是否必需 | 使用方 | 说明 |
|---|---|---|---|
PUBLIC_CLERK_PUBLISHABLE_KEY | 是 | web | Clerk 公钥。旧变量 NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY 仍兼容。 |
CLERK_SECRET_KEY | 是 | api/auth | Clerk 服务端密钥 |
CLERK_WEBHOOK_SECRET | 是 | api | /api/v1/webhook/clerk 使用 |
API 运行与部署变量
API 前缀需要区分挂载入口:
- Next.js
ship路由入口:/api/v1/* - 独立
ship-api服务:/v1/*
如果不配置 PUBLIC_SERVER_URL,Web 会走 SITE_URL 下的同源 /api/v1。只有浏览器要访问的 API origin 和 SITE_URL 不一致时才设置它。有 VERCEL_ENV 时,浏览器请求会走 ${PUBLIC_SERVER_URL}/api/v1;没有 VERCEL_ENV 时,会走 ${PUBLIC_SERVER_URL}/v1,用于独立 ship-api 或 Worker 风格 API origin。
| 变量 | 是否必需 | 说明 |
|---|---|---|
PORT | 否(默认 9001) | API 监听端口 |
APP_ENV | 建议 | development / production 等环境标识 |
NODE_ENV | 建议 | Node 运行环境 |
JWT_SECRET | 建议 | API JWT 签名密钥(虽有 fallback,不建议用于生产) |
JWT_EXPIRATION | 否(默认 7d) | API JWT 有效期 |
GIT_COMMIT_SHA | 否 | 日志构建信息 |
BUILD_TIME | 否 | 日志构建信息 |
CROSS_SUB_DOMAIN | 可选 | 跨子域 Cookie 域名 |
生产地址变量
更新 Infisical、容器运行时 env 或主机环境变量时,以这张表为准。
| 变量 | 线上处理 | 是否必需 | 说明 |
|---|---|---|---|
SITE_URL | 保留/新增 | 是 | 站点 canonical origin,例如 https://app.example.com |
TRUSTED_ORIGINS | 保留/新增 | 建议 | Better Auth 与 CORS 使用的可信来源,多个值逗号分隔 |
BETTER_AUTH_URL | 按需保留 | 否 | 默认回退到 SITE_URL;只有 auth/API 对外 origin 不同时才设置 |
VERCEL_ENV | 保留/默认 | 否 | Docker build arg 默认 production;Cloudflare/Vercel runtime 建议显式保持 production |
APP_ENV | 保留 | 建议 | 业务/运行环境标识 |
PUBLIC_SERVER_URL | 按需设置 | 否 | 仅当浏览器访问的 API origin 与 SITE_URL 不一致时设置;有 VERCEL_ENV 时目标暴露 /api/v1,否则暴露 /v1;旧变量 NEXT_PUBLIC_SERVER_URL 仍兼容 |
NEXT_PUBLIC_SITE_URL | 删除 | 否 | 已替换为 SITE_URL |
NEXT_PUBLIC_API_PREFIX | 删除 | 否 | 已替换为 shared 常量 |
CORS_ORIGIN | 删除 | 否 | 已替换为 TRUSTED_ORIGINS |
API_PREFIX | 删除 | 否 | 前缀不再由 env 控制 |
GitHub Variables for Infisical OIDC(GHCR Docker build)
这些值写在 GitHub Repository Variables,不写在代码里。入口:
GitHub repo > Settings > Secrets and variables > Actions > Variables > New repository variable。
这些变量用于 publish-ghcr.yml 的 Load Infisical secrets。它们只是让 GitHub Actions 通过 OIDC 找到 Infisical project/env/folder,不是业务运行时 secret。Trigger deploy 不需要这组 Infisical OIDC 变量。
publish-ghcr.yml 已经声明 permissions.id-token: write,所以 OIDC auth method not found for identity 通常不是 GitHub Actions 没权限发 OIDC token,而是 Infisical 里这个 identity 还没有配置 OIDC Auth。默认创建的 machine identity 可能带的是 Universal Auth;用于 GitHub Actions 时,需要打开 INFISICAL_IDENTITY_ID 对应的 identity,把 Universal Auth 移除/替换为 OIDC Auth。
| GitHub Variable | 从 Infisical 哪里拿 | 说明 |
|---|---|---|
INFISICAL_IDENTITY_ID | Infisical > Project > Access Control > Machine Identities,打开给 GitHub Actions 用的 identity;或从 Organization Settings > Access Control > Identities 找到同一个 identity | 不是 secret,是 OIDC machine identity 的公开 ID。Authentication 里必须有 OIDC Auth;OIDC Discovery URL 和 Issuer 都用 https://token.actions.githubusercontent.com。本 workflow 使用 GitHub Environment,main 的 Subject 建议锁到 repo:<owner>/<repo>:environment:Production,dev 锁到 repo:<owner>/<repo>:environment:staging;只有不使用 environment 的 workflow 才用 repo:<owner>/<repo>:ref:refs/heads/main。 |
INFISICAL_PROJECT_SLUG | Infisical > Bunship project > Project Settings > General > Project Slug | 必须和 workflow 访问的 Infisical project slug 完全一致。 |
INFISICAL_ENV_SLUG | Infisical > Bunship project > Project Settings > Environments,复制生产环境 slug | 例如 prod / production,区分大小写,必须和 Infisical 环境 slug 完全一致。 |
INFISICAL_DOMAIN | 当前 Infisical 实例入口域名 | Infisical Cloud 默认是 https://app.infisical.com,可以不填;自建实例才需要填自建域名。 |
INFISICAL_SECRET_PATH | Infisical project 里的 secret folder path | 可选,默认 /。如果 folder 是根目录下的 env,填 /env。这不是 INFISICAL_ENV_SLUG。 |
INFISICAL_RECURSIVE | GitHub Repository Variables | 可选,默认 false。只有 Docker build 输入放在 INFISICAL_SECRET_PATH 下面的子目录里才填 true。 |
排障顺序:
OIDC auth method not found for identity:到Project > Access Control > Machine Identities打开INFISICAL_IDENTITY_ID对应 identity,确认 Authentication 里有 OIDC Auth;如果还是 Universal Auth,删除/替换为 OIDC Auth。- Subject 不匹配:因为 workflow 使用 GitHub Environment,生产分支默认 subject 是
repo:<owner>/<repo>:environment:Production,不是repo:<owner>/<repo>:ref:refs/heads/main。 Folder with path '/' ... was not found:INFISICAL_ENV_SLUG或INFISICAL_SECRET_PATH不匹配。folder 在根目录下叫env时,INFISICAL_SECRET_PATH要填/env。
workflow 入口:
| 文件 | 用途 |
|---|---|
.github/workflows/publish-ghcr.yml | GHCR Docker image build/push;执行 Load Infisical secrets,把结果作为 BuildKit secret file 传给 Dockerfile。 |
.github/workflows/deploy-trigger.yml | Trigger.dev deploy;不走 Infisical OIDC,使用 GitHub Secrets/Variables 中的 Trigger deploy 与同步变量。 |
GHCR 构建输入
私有 GHCR 镜像由 .github/workflows/publish-ghcr.yml 使用 Dockerfile 构建。工作流会把 Better Auth 镜像发布到默认 tag(latest、分支 tag、<branch>-<sha>),把 Clerk 镜像发布到 clerk-latest / clerk-<sha>。运行时 env 和运行时 public config 都由容器启动后的环境变量注入,因此同一个 provider 专用镜像可以复用到不同环境。
Build args:
| 变量 | 是否必需 | 说明 |
|---|---|---|
GIT_COMMIT_SHA | 否 | workflow 使用 GitHub SHA;默认 local |
VERCEL_ENV | 否 | 默认 production |
PUBLIC_AUTH_PROVIDER | 由 workflow matrix 提供 | 默认 tag 使用 better-auth,clerk-* tag 使用 clerk |
BuildKit secret file:workflow 会把 Infisical 读取结果写入 .infisical.docker.env,并以 infisical_env 传给 Docker build。Dockerfile 只在构建时读取这份文件,不会把文件复制进镜像层。这里可以放 VERCEL_ENV、PUBLIC_* 这类构建期/公开配置;不应该要求 SITE_URL、DATABASE_URL、S3_*、auth secrets、Stripe keys、email keys 这些运行时变量。Docker build 会设置 SKIP_ENV_VALIDATION=1,这些业务变量属于容器 runtime env。
OAuth 变量不属于 Docker build。OAUTH_GITHUB_CLIENT_ID / OAUTH_GOOGLE_CLIENT_ID 只在启用 GitHub/Google 登录时,作为 Web/API auth runtime 配置使用,并且要和对应的 *_CLIENT_SECRET 一起在容器运行时注入。Trigger/AI runtime 不需要这两个变量。
Trigger dispatch 变量也不属于 Docker build。TRIGGER_SECRET_KEY 只应该放在 Web/API 容器 runtime,用于选择 Trigger adapter 并向 Trigger.dev 投递任务;不要放进 GHCR build secrets。
容器运行时 public config:
| 变量 | 是否必需 | 旧变量 fallback | 说明 |
|---|---|---|---|
PUBLIC_S3_URL_BASE | 是 | NEXT_PUBLIC_S3_URL_BASE | 公网 CDN/对象地址前缀 |
PUBLIC_OAUTH_GOOGLE_CLIENT_ID | 启用 Google OAuth 时必需 | NEXT_PUBLIC_OAUTH_GOOGLE_CLIENT_ID | Google One Tap / 登录按钮的公共 Client ID |
PUBLIC_CLERK_PUBLISHABLE_KEY | 启用 Clerk 时必需 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY | Clerk 公钥 |
PUBLIC_SERVER_URL | 否 | NEXT_PUBLIC_SERVER_URL | 与 SITE_URL 不一致时的可选 API origin 覆盖;有 VERCEL_ENV 时目标暴露 /api/v1,否则暴露 /v1 |
PUBLIC_GA_ID | 否 | NEXT_PUBLIC_GA_ID | Google Analytics |
PUBLIC_UMAMI_DATA_ID | 否 | NEXT_PUBLIC_UMAMI_DATA_ID | Umami 统计 |
可选功能变量
| 变量 | 功能 |
|---|---|
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | KV/Redis 功能 |
TRIGGER_SECRET_KEY, TRIGGER_PROJECT_ID | Web/API runtime 投递 Trigger.dev 任务;不要放进 Docker build |
REDIS_URL | BullMQ/CMS 队列、AI 限流/准入指标;使用 BullMQ 时必填 |
CRON_SECRET | /api/cron/ai-cleanup cron 路由鉴权 |
PUBLIC_GA_ID, PUBLIC_UMAMI_DATA_ID | 分析统计;旧的 NEXT_PUBLIC_* 名称仍兼容 |
NEXT_PUBLIC_APP_VERSION, VERCEL_GIT_COMMIT_SHA | UI 版本展示 |
BETTER_UPLOAD_PROVIDER | 上传 provider 兜底;详见上方上传 provider 小节 |
OPENAI_API_KEY, OPENAI_API_BASE | 管理端 AI 命令/协作 |
REPLICATE_API_TOKEN | Replicate 服务 |
KIE_API_KEY, KIE_API_BASE_URL | KIE 服务 |
FAL_API_KEY | FAL 服务 |
本地 .env 示例(Better Auth)
PUBLIC_AUTH_PROVIDER=better-auth
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/bunship
BETTER_AUTH_SECRET=replace-with-long-random-secret
AUTH_SECRET=replace-with-long-random-secret
SITE_URL=http://localhost:3001
TRUSTED_ORIGINS=http://localhost:3001,http://localhost:9001
ADMIN_EMAIL_LIST=admin@example.com
EMAIL_FROM=Bunship <noreply@example.com>
RESEND_API_KEY=re_xxx
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
S3_ENDPOINT=https://s3.example.com
S3_REGION=auto
S3_ACCESS_KEY=xxx
S3_SECRET_KEY=xxx
S3_BUCKET=bunship
PUBLIC_S3_URL_BASE=https://cdn.example.com
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
CREEM_API_KEY=creem_xxx
CREEM_WEBHOOK_SECRET=creem_whsec_xxx
PAYMENT_PROVIDER_DEFAULT=stripe
S_GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx
CLOUDFLARE_ACCOUNT_ID=xxx本地 .env 示例(Clerk)
PUBLIC_AUTH_PROVIDER=clerk
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/bunship
SITE_URL=http://localhost:3001
TRUSTED_ORIGINS=http://localhost:3001,http://localhost:9001
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
CLERK_SECRET_KEY=sk_test_xxx
CLERK_WEBHOOK_SECRET=whsec_xxx
ADMIN_EMAIL_LIST=admin@example.com
EMAIL_FROM=Bunship <noreply@example.com>
RESEND_API_KEY=re_xxx
S3_ENDPOINT=https://s3.example.com
S3_REGION=auto
S3_ACCESS_KEY=xxx
S3_SECRET_KEY=xxx
S3_BUCKET=bunship
PUBLIC_S3_URL_BASE=https://cdn.example.com
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
S_GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxx
CLOUDFLARE_ACCOUNT_ID=xxx