在 DigitalOcean App Platform 上部署 Manifest:Web 服务 + Dev PostgreSQL + Space 对象存储的完整实战指南
【免费下载链接】llm-gatewayConnect Your Agents And Harnesses With Any Provider 🦚项目地址: https://gitcode.com/GitHub_Trending/manifest7/llm-gateway
本指南基于仓库 deploy/digitalocean/TUTORIAL.md 展开,讲解如何通过 DigitalOcean 官方的 Deploy to DigitalOcean 流程,把 Manifest(开源 AI 模型网关)部署为 App Platform 上的 Web 服务,并搭配 Dev PostgreSQL 数据库与用户自备的 DigitalOcean Space 对象存储,用于持久化请求记录(request recordings)。读完本文,你将掌握 App Platform 部署模板的完整参数填写方法、密钥生成规范、S3 兼容存储的配置要点,以及部署后的健康检查与生产化建议;文中同时结合 app.config.ts、request-recording-storage.service.ts 等源码,说明每个环境变量的底层作用。
一、部署方案总览
Manifest 是一个 OpenAI 兼容的 AI 模型路由网关,可连接各类 LLM 提供商与 Agent。在 DigitalOcean 上,官方推荐的部署形态是一套三件套组合:
| 组件 | 类型 | 用途 |
|---|---|---|
| App Platform Web Service | 应用服务 | 承载 Manifest 后端与内置前端页面 |
| Dev PostgreSQL | 托管数据库 | 应用主数据库(开发档) |
| DigitalOcean Space | 用户自备对象存储 | 持久化请求记录(request recordings) |
整个部署通过 DigitalOcean 的Deploy to DigitalOcean流程完成:该按钮会读取公开仓库中的.do/deploy.template.yaml部署模板,据此在 App Platform 上创建应用。需要注意的是,App Platform 没有持久化卷(no persistent volumes),其本地文件系统是临时性的(ephemeral),因此任何需要跨重启、跨实例保留的数据——尤其是请求记录——都必须放在外部持久化存储中,这就是引入 Space 的原因。同时,部署模板无法自动创建带限定权限(scoped)的 Space 凭证,所以 Space 与受限访问密钥必须在部署前由你在 DigitalOcean 控制台手动创建。
该方案会创建付费资源(App Platform 服务、Dev PostgreSQL、Space),部署前请确认账号已开通计费(billing)且目标区域可用 App Platform。
二、前置条件:账号与 Space 准备
开始部署前,你需要满足以下前置条件:
- 一个已开通计费(billing enabled)的 DigitalOcean 账号;
- 所选区域(region)内可访问 App Platform;
- 一个私有(private)Space,以及该 Space 专属的受限 Read/Write/Delete 访问密钥。
准备 Space 时,请在 DigitalOcean 控制台完成如下动作并记录四个信息:
- Space 名称(对应
REQUEST_RECORDING_S3_BUCKET); - 区域端点(region endpoint),格式形如
https://nyc3.digitaloceanspaces.com; - 访问密钥 ID(access key);
- 密钥(secret key)。
建议为 Space 创建受限(limited)访问密钥,只授予该 Space 的读、写、删除权限,而不是使用具有全局权限的密钥——这与仓库中其他平台模板的安全做法一致(例如 app.json 中要求 access key 仅限读写删除录制 bucket 内的对象)。
三、发起部署:Deploy to DigitalOcean 流程
准备好上述信息后,在浏览器打开 DigitalOcean 的部署入口:
https://cloud.digitalocean.com/apps/new?repo=https://github.com/mnfst/manifest/tree/mainDigitalOcean 会读取仓库的部署模板,并在正式部署前提示你填写缺失的 secret 值。此时需要你为下列敏感变量生成并填入独立的值。
四、密钥生成与核心环境变量详解
4.1 生成两份相互独立的随机密钥
文档明确要求为BETTER_AUTH_SECRET与MANIFEST_ENCRYPTION_KEY各生成一个独立的随机值,使用命令:
openssl rand -hex 32每次执行都会输出一个 64 位十六进制字符串(32 字节)。请分别运行两次,得到两个不同的值,一个用于BETTER_AUTH_SECRET,另一个用于MANIFEST_ENCRYPTION_KEY,切勿复用同一个值。
为什么必须两个都填且互不相同?packages/backend/.env.example 给出了清晰的源码级解释:
BETTER_AUTH_SECRET是会话(session cookie)签名密钥,最小 32 字符;MANIFEST_ENCRYPTION_KEY是静态加密(at-rest encryption)密钥,用于加密存储的 LLM 提供商 API Key、OAuth Token 以及请求记录本身。AES-256-GCM 密钥由该 secret 通过 scrypt 派生;- 若
MANIFEST_ENCRYPTION_KEY未设置,Manifest 会回退使用BETTER_AUTH_SECRET作为加密密钥。这样虽然省事,但一旦会话 cookie 泄露,攻击者就能解密所有已存储的提供商凭证——两个泄露面完全重叠。设置两个独立值,能让两条泄露路径互不关联。
另外请注意:静态加密密钥一旦变更,旧的已存储请求记录将无法读取,会被保留期策略清理,因此生产环境应长期妥善保管这两个 secret。
4.2 请求记录的 S3 相关变量
部署时需填写的 Space 相关变量如下:
| 环境变量 | 填写内容 | 说明 |
|---|---|---|
REQUEST_RECORDING_S3_BUCKET | 你的 Space 名称 | 存放请求记录的 bucket |
REQUEST_RECORDING_S3_ENDPOINT | https://<space-region>.digitaloceanspaces.com | Space 的区域端点,用于定位实际 Space 所在区域 |
REQUEST_RECORDING_S3_ACCESS_KEY_ID | 受限 Space 访问密钥 ID | 仅需该 Space 的读写删权限 |
REQUEST_RECORDING_S3_SECRET_ACCESS_KEY | 对应 secret | 与上面的 access key 配对 |
REQUEST_RECORDING_S3_REGION | 保持us-east-1 | 按文档要求保持默认值 |
REQUEST_RECORDING_S3_FORCE_PATH_STYLE | 保持false | 使用虚拟主机风格(virtual-hosted style)URL |
其中后两个变量的说明值得展开:REQUEST_RECORDING_S3_REGION=us-east-1需要原样保留,因为 DigitalOcean 的 AWS SDK 指引(guidance)是通过endpoint来实际选择 Space 所在区域的,region 仅用于请求签名;REQUEST_RECORDING_S3_FORCE_PATH_STYLE=false则让 SDK 采用虚拟主机风格寻址而非路径风格,这是 DigitalOcean Spaces 兼容 AWS S3 的默认行为。
这些变量在源码中的定义见 packages/backend/src/config/app.config.ts,它们被逐一读取并注册为 NestJS 配置项:
requestRecordingS3Bucket: process.env['REQUEST_RECORDING_S3_BUCKET'] ?? '', requestRecordingS3Endpoint: process.env['REQUEST_RECORDING_S3_ENDPOINT'] ?? '', requestRecordingS3Region: process.env['REQUEST_RECORDING_S3_REGION'] ?? '', requestRecordingS3AccessKeyId: process.env['REQUEST_RECORDING_S3_ACCESS_KEY_ID'] ?? '', requestRecordingS3SecretAccessKey: process.env['REQUEST_RECORDING_S3_SECRET_ACCESS_KEY'] ?? '', requestRecordingS3ForcePathStyle: process.env['REQUEST_RECORDING_S3_FORCE_PATH_STYLE'] === 'true',4.3 底层实现:S3RecordingStorage 如何工作
request-recording-storage.service.ts 中的S3RecordingStorage类展示了这些配置的真实用途:它基于@aws-sdk/client-s3构造S3Client,将region、endpoint、forcePathStyle与可选的凭证对直接传入:
new S3Client({ region: config.region, endpoint: config.endpoint, forcePathStyle: config.forcePathStyle, credentials: config.accessKeyId && config.secretAccessKey ? { accessKeyId: config.accessKeyId, secretAccessKey: config.secretAccessKey } : undefined, });写入对象时,PutObjectCommand将ContentType显式设为application/octet-stream——这是因为请求记录正文是客户端侧密文(见 request-recording-codec.ts 的压缩加密逻辑),若声明为 gzip 内容编码,某些 S3 兼容存储或 CDN 会在 GET 时尝试透明解压,导致数据损坏。
对象在 Space 中的键名遵循如下层级结构(来自 objectKey 方法):
request-recordings/v1/tenants/{tenantId}/requests/{requestId}/attempts/{attemptId}.json.gz这也印证了文档中"请求记录是 Space 中的私有对象"的说法——每条请求、每次重试尝试(attempt)都有独立的对象,便于审计与追溯。
五、PostgreSQL 连接:uselibpqcompat 与 sslmode=require
部署模板会自动把uselibpqcompat=true追加到 DigitalOcean 的 PostgreSQL URL 上。这样做的原因是:DigitalOcean 生成的连接串默认带sslmode=require,而 Node 生态的pg驱动在解析这种连接串时需要兼容性开关才能正确处理;uselibpqcompat=true让pg采用 libpq 兼容的解析方式,从而在 TLS 连接下正常工作。
这一处理并非 DigitalOcean 独有——仓库中 deploy/aws/manifest.yaml 的 AWS 部署同样在连接串中拼接了?uselibpqcompat=true&sslmode=require,可见这是 Manifest 在托管 PostgreSQL 平台上统一使用的连接串约定。如果你在其他平台(如 Koyeb)上部署,deploy/koyeb/TUTORIAL.md 也给出了类似要求:连接串需带sslmode=require(无查询参数时用?,已有参数时用&追加)。
由于 App Platform 通过环境变量注入数据库连接串,你不需要(也不应该)手动修改DATABASE_URL,模板会自动完成上述追加处理。
六、验证部署与创建首个管理员
App Platform 完成部署后:
- 打开应用 URL(形如
https://<your-app>.ondigitalocean.app),创建第一个账号。第一个注册的账号将成为管理员(admin),因此请在公开分享应用地址前完成这一步; - 验证后端健康状态,打开:
https://<your-app-url>/api/v1/health该健康检查端点在源码中位于 health.controller.ts,路由为GET /api/v1/health,正常返回:
{ "status": "healthy", "uptime_seconds": 42 }值得注意的细节是:当进程进入优雅关停(draining)状态时,该端点会返回503并携带{ "status": "shutting_down", ... },让平台边缘停止向该副本路由新流量——这也是 App Platform 滚动部署期间判断实例是否健康的重要依据。
七、注意事项与生产化建议
7.1 Dev Database 仅适合起步
DigitalOcean 的部署按钮目前只支持公开仓库与 Dev Database。Dev 档数据库适合试用与开发环境;生产数据请从 App Platform 设置中将 Dev Database 升级为托管数据库(managed database),以获得更优的性能、备份与高可用能力。
7.2 切勿移除 S3 变量
如第一节所述,App Platform 的本地文件系统是临时性的,应用重启或实例重建都会清空。因此除非你明确禁用记录持久化,否则不要移除上述REQUEST_RECORDING_S3_*变量。Manifest 的存储选择默认是auto能力检测模式(见 app.config.ts):完整且配对的 S3 凭证优先;无 S3 凭证时自托管部署才会回退到挂载的文件系统。在 App Platform 上,唯一合理的回退就是 Space,移除这些变量等价于放弃记录持久化。
7.3 删除应用不会删除 Space
删除 App Platform 应用不会级联删除 Space。如果你不再使用,请先导出需要的请求记录,再单独清理 Space,避免数据永久丢失。
7.4 其他值得关注的生产配置
结合仓库中其他平台部署文档与 .env.example,生产环境还建议关注:
MANIFEST_MODE=selfhosted与BIND_ADDRESS=0.0.0.0:让后端监听所有网卡接口,供平台边缘路由流量(参考 app.json 与 Koyeb 模板的做法);NODE_ENV=production:以生产模式运行后端;DB_POOL_MAX:按数据库连接上限控制连接池大小,避免连接数打满(App Platform 的托管数据库同样有连接数限制,参见 deploy/heroku/TUTORIAL.md 中按档位调小池的同类处理)。
结语
在 DigitalOcean App Platform 上部署 Manifest 的核心思路可以概括为三句话:用 App Platform 跑无状态应用、用 Dev/托管 PostgreSQL 存业务数据、用私有 Space 持久化请求记录。只要在部署前把私有 Space 与受限密钥准备好,按模板要求填写两组独立的随机密钥和六个 S3 变量,保持REQUEST_RECORDING_S3_REGION=us-east-1与REQUEST_RECORDING_S3_FORCE_PATH_STYLE=false不变,即可顺利完成部署,并通过/api/v1/health快速验证。仓库中 deploy/ 目录还提供了 AWS、Fly、GCP、Coolify、EasyPanel、Koyeb、Heroku 等平台的同类指南,可作为多平台部署的对照参考。
【免费下载链接】llm-gatewayConnect Your Agents And Harnesses With Any Provider 🦚项目地址: https://gitcode.com/GitHub_Trending/manifest7/llm-gateway
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考