安装 Nessie
在你选择的基础设施上运行 Nessie,从单台机器到托管云均可。
开始之前需要准备什么
Nessie 是由 Node 服务和两个静态包组成的 pnpm monorepo。无论采用哪种运行方式,都需要先具备以下四项。
- 带有
pgvector的 PostgreSQL。这一项不可省略,也无法替代。迁移会执行CREATE EXTENSION vector,并创建vector(1024)列。生产环境运行的是pgvector/pgvector:pg17。 - Node 22 和 pnpm。
infrastructure/docker/中的每个 Dockerfile 都基于node:22-slim构建,仓库根目录的package.json也固定了包管理器版本(pnpm@10.22.0)。 - 一个兼容 OpenAI 的模型端点及其密钥。智能体和向量嵌入都通过它调用。参见下文的“配置推理服务”。
- 兼容 S3 的对象存储,适用于所有非本地开发的部署。在
local模式之外,使用本地磁盘存储上传文件会被拒绝,而且这种拒绝会导致启动失败,而不是降级启动。参见“单机还是托管云”。
使用下文介绍的容器部署方式需要 Docker 和反向代理;任何需要从互联网访问的部署,还需要域名和 TLS。
仓库中没有说明可支撑特定人数所需的最低 CPU、内存和磁盘配置,因此本页也不作说明。
各组件及其协作方式
| 组件 | 作用 | 对外访问 |
|---|---|---|
api/ | 基于 Fastify 的 HTTP/WebSocket API。所有客户端都只与它通信。 | 是 |
worker/ | 智能体编排与后台运行。轮询任务队列,不绑定端口。 | 否 |
admin/ | 产品界面:由 Vite 构建的静态包,通过 nginx 提供服务。 | 是 |
web/ | 公开网站。静态页面,可选,与产品的运行无关。 | 是 |
| PostgreSQL | 数据库、任务队列和实时消息总线,三者合一。 | 否 |
| 对象存储 | 附件、头像和知识库文件。兼容 S3,生产环境使用 MinIO。 | 否 |
gateway/ | 可选的推送中继,用于 APNs 和 FCM。除非你主动启用,否则不会运行。 | 是 |
API 和 worker 使用同一个镜像。两个包紧密相连,因此 Dockerfile.app 会一次性构建整个团队的代码,worker 容器则把命令覆盖为 node worker/dist/index.js。在 local 模式下完全没有 worker 容器:API 会在自己的进程中嵌入运行 worker。
没有 Redis。任务队列和实时传输都基于 Postgres 实现。这是有意的设计,而不是疏漏。Google Cloud 版本的配置同样因此去掉了 Pub/Sub 和 Redis 模块。
端口
本地开发时,API 默认使用 5454,管理后台默认使用 5455。每个检出副本可以通过在环境变量或仓库的 .env 中设置 NESSIE_API_PORT 和 NESSIE_ADMIN_PORT,使用自己的一对端口;仓库中的每个工具(包括浏览器测试套件)读取的都是这同样两个值。请在启动前选好这一对端口并保持不变:把正在运行的服务改到另一个端口,会让管理后台连到错误的 API。
生产环境不受这一规则约束。API 容器内部监听 5554(由 NESSIE_API_PORT 固定),推送中继监听 5556,两者都位于反向代理之后。
在一台机器上进行开发运行
仓库自带一个 Compose 文件,其中只有一个数据库,这已足够支撑本地开发流程。
pnpm install
docker compose -f infrastructure/compose/docker-compose.yml up -d postgres该容器将 Postgres 发布在 55432 端口,用户名、密码和数据库名均为 nessie。把连接字符串写入仓库根目录的 .env 文件(API 的开发脚本会读取它),然后执行一次迁移并构建 worker。因为在 local 模式下,API 会从构建后的 dist 中加载 worker:
DATABASE_URL=postgresql://nessie:nessie@localhost:55432/nessie
pnpm --filter @nessie/api prisma:migrate:deploy
pnpm --filter @nessie/worker build
pnpm devpnpm dev 会在 5454 和 5455 端口上并行运行 API 和管理后台,并支持热重载。请用 GET /api/health 确认 API 已正常响应。
Postgres 与 pgvector 要求
迁移本身会创建三个扩展:vector、pg_trgm 和 pgcrypto,因此执行迁移的数据库角色必须有权限创建扩展。在托管的 Postgres 上,这是首先需要确认的事项。
向量宽度是数据库结构的一部分,而不是可配置项。thoughts.embedding、thought_recalls.query_embedding 和 knowledge_page_chunks.embedding 都是 vector(N) 列,N 在 packages/schemas/src/embedding.ts 中统一定义为 EMBEDDING_DIMENSIONS,目前为 1024,即生产环境所用向量模型的原生维度。每个嵌入请求都会发送这一宽度,因此服务商若返回其他维度,请求会直接失败,而不会写入数据库拒绝的向量。若要切换到其他维度的嵌入模型,需要修改该常量、编写迁移以重新定义这三列的类型,并重新生成全部嵌入:不同宽度的向量无法互相转换,因此迁移会将它们置为空值,而不是截断。
如果你把 Nessie 与其他应用部署在同一个共享 Docker 网络中,请为其数据库容器取一个独特的名称。项目自带的 Compose 服务名为 nessie-postgres,而不是 postgres。原因是服务名会在共享网络中成为 DNS 别名,一旦重名,postgres:5432 就会在两个数据库之间轮询。
配置
运行时配置采用分层方式:一个以只读方式挂载到 API 和 worker 中的 nessie.config.json 文件作为基础,再由环境变量覆盖其中的设置。两者之间的映射定义在 packages/config 中的 ConfigEnvMap 里,它是权威的完整清单。
NESSIE_MODE 决定部署形态:local(默认值,也是开发者使用的方式)、hosted 或 selfHosted。自行托管安装应设为后者。该模式会禁用开发登录,并要求配置 CORS 允许列表。
以下是 selfHosted 部署在缺少这些设置时将无法启动或无法正常工作的配置项:
| 变量 | 原因 |
|---|---|
DATABASE_URL / NESSIE_DB_URL | Postgres 连接字符串。 |
NESSIE_AUTH_SECRET | 32 字节的十六进制字符串(openssl rand -hex 32)。用于签署会话和引导令牌。在 local 模式之外,未设置时启动会报错;每个副本以及每次部署之间必须使用相同的值,否则每次发布都会使所有会话失效。 |
NESSIE_ENCRYPTION_KEY_RING, NESSIE_ENCRYPTION_ACTIVE_KEY_VERSION | JSON 格式的密钥环,以及用于选择当前根密钥的标签,在 local 模式之外为必填项。它与签名密钥是各自独立的根密钥:密钥环用于加密 Nessie 保存的所有持久化机密。切勿让两者复用同一个值。 |
NESSIE_STORAGE_PROVIDER | 必须为 s3。filesystem 是默认值,但在 hosted 和 selfHosted 模式下会被拒绝。这样一来,未设置此项的部署会直接启动失败,而不会把上传文件写入其他容器读不到的磁盘。 |
NESSIE_CORS_ORIGINS | 管理后台的来源地址。管理后台与 API 是两个独立的来源,API 必须把它所服务的来源加入允许列表。 |
NESSIE_API_PUBLIC_URL, NESSIE_ADMIN_PUBLIC_URL | 用于在 HTTP 请求之外生成 OAuth 重定向 URI。若未设置第一项,API 会在处理请求时抛出错误。 |
NESSIE_MODEL_PROVIDER, _BASE_URL, _API_KEY | 推理请求的去向。见下文。 |
infrastructure/compose/.env.prod.example 列出了所有配置项的确切格式,可选的配置块(对象存储、机密保险库、SSO、已连接邮箱)都以注释形式写在对应位置。完整清单(包括所有可选设置)位于 docs/deployment/configuration.md。
使用 Docker Compose 部署
将 .env.prod.example 复制为 docker-compose.prod.yml 同目录下的 .env,填写其中的内容,然后启动数据库、构建、执行迁移并启动服务。顺序很重要:迁移和种子数据必须在数据库已启动、而服务尚未对外提供之前运行。
COMPOSE="docker compose -f infrastructure/compose/docker-compose.prod.yml"
bash infrastructure/compose/ensure-encryption-key-ring.sh infrastructure/compose/.env
$COMPOSE up -d nessie-postgres
$COMPOSE build nessie-api nessie-admin nessie-web
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api prisma:migrate:deploy
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:connectors
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api seed:apps
$COMPOSE run --rm --no-deps nessie-api pnpm --filter @nessie/api reconcile
$COMPOSE up -d生产环境的 Dockerfile 会先对每个包运行 lint,因此 lint 失败即意味着构建失败。
reconcile 步骤不可省略
reconcile 会为每个组织写入默认策略规则,并补齐受保护的 MCP 和个人助理工具授权。过去这些操作由 API 在每个副本启动时完成,现在 API 只负责连接、监听,不再做其他事情,因此如果跳过这一步,第一个组织就没有任何策略规则,所有智能体绑定都会被拒绝。该步骤是幂等的,每次部署都执行也没有额外代价;手动应用的升级同样必须运行它。
第一个所有者账户
全新的 selfHosted 安装没有任何用户,因此 API 会生成一个一次性的引导令牌,并记录一个设置地址。该令牌保存在 Postgres 中,而不是某个进程里,因此所有副本对它的认定一致。
docker compose -f infrastructure/compose/docker-compose.prod.yml logs nessie-api 2>&1 | grep bootstrap打开 https://<your-admin-host>/bootstrap?token=<token>,创建所有者账户。令牌 15 分钟后过期;如需新令牌,请重启 API 服务。
如果你配置了 SSO,则完全不需要引导步骤。一旦启用外部身份验证服务商,引导模式就会被关闭,否则它会劫持登录页面。第一个通过 SSO 登录的人会自动创建默认团队,并成为该团队的所有者。
配置推理服务
对话功能兼容 OpenAI 接口。NESSIE_MODEL_BASE_URL 是端点地址,NESSIE_MODEL_API_KEY 是 Bearer 令牌,NESSIE_MODEL_PROVIDER 用于指定适配器。Nessie 内置了 openai、kimi 和 deepseek 适配器,其他服务则通过通用的兼容 OpenAI 连接器接入。NESSIE_MODEL_NAME 设置该部署的默认模型,但单个智能体自己选择的模型优先级更高。
向量嵌入是单独路由的,因为对话服务商可能根本不提供嵌入服务——例如 DeepSeek 就没有嵌入接口。每个未设置的 NESSIE_EMBEDDING_* 字段都会继承对话服务商的配置,因此若部署完全不设置这些项,嵌入请求就会经由对话端点发出。当对话服务商无法提供嵌入时,请设置 NESSIE_EMBEDDING_PROVIDER、NESSIE_EMBEDDING_MODEL,以及在按服务路由的代理上设置服务段。这类配置错误不会大声报错,而是悄无声息地失败:记忆召回和知识库搜索会降级为词法通道并继续运行,同时记录 kb_search: query embedding failed。
Nessie 自身的支出核算(按组织的令牌账本和预算闸门)不受所指向端点的影响,无论是否配置了带签名的逐次调用归属信息,其表现都完全一致。
登录
Nessie 通过 UnlikeOtherAI 进行身份验证,该服务同时管理组织与团队结构。管理后台的登录页只显示一个“登录 / 注册”按钮。
这一集成采用的是 config-JWT 流程,而非标准 OIDC:API 在 GET /api/auth/sso/config 提供经签名的 RS256 配置文档,并在 GET /.well-known/jwks.json 提供对应的 JWKS,两者都位于 API 主机上。部署它只需一次性完成以下步骤:生成 RSA-2048 密钥对,并设置 UOA_CONFIG_JWT_PRIVATE_KEY_B64、UOA_CONFIG_JWT_KID、UOA_DOMAIN、UOA_CONFIG_URL、UOA_JWKS_URL、UOA_REDIRECT_URL 和 UOA_CONTACT_EMAIL;点击一次按钮以提交集成申请;等待申请获批,批准后会向联系邮箱发送一个一次性链接,其中包含客户端密钥;然后设置 UOA_CLIENT_SECRET 并重启 API。同一域名下的 kid 必须唯一。
通过域名与 TLS 对外提供服务
一个部署会提供三个域名:公开网站、管理后台和 API。管理后台与 API 的来源地址不可互换。Dockerfile.admin 会在构建时把 VITE_API_BASE_URL 写入包中,因此每个构建出的管理后台产物(包括内嵌它的桌面版)都必须针对 API 来源进行构建。如果改为针对管理后台自身的来源构建,/api/auth/providers 就会解析为管理后台的 HTML 外壳,导致登录页停留在“Loading providers...”。
生产环境在 Caddy 处终止 TLS,Caddy 通过 HTTP-01 为每个主机名获取证书,无需 DNS 服务商插件。请把代理指向管理后台和网站容器的 80 端口,以及 API 的内部端口。
将 NESSIE_API_TRUSTED_PROXY_HOPS 设为 API 前面的代理数量,单个反向代理时为 1。默认值为 0,此时会完全忽略 X-Forwarded-For。这一项设置会带来两个后果:它同时决定认证限流的键值,因此设得太低,所有客户端都会共享代理的同一个配额;设得太高,客户端又可以伪造自己的地址。宁可偏低,并加以测量。
为每个组织和团队分配独立子域名是可行的,但默认关闭。它由 NESSIE_TEAM_HOST_BASE_DOMAIN 控制:未设置时,租户根本不会通过主机名路由。证书签发的门控 NESSIE_TLS_CHECK_KEY 在未设置时会拒绝一切请求,对于不使用该功能的部署而言,这是正确的默认值。
健康检查
| 端点 | 回答的问题 |
|---|---|
GET /api/health | 进程是否存活?只有在开始排空之后才会失败。 |
GET /api/health/ready | 该副本能否接收新请求?负载均衡器应指向这个端点。 |
GET /api/ops/health | 部署运行状况如何?这是一份面向人的报告,包括 worker 心跳、队列深度和失败任务,而不是探针。 |
收到 SIGTERM 时,API 会排空请求,而不是直接终止。NESSIE_SHUTDOWN_TIMEOUT_MS 限定排空的时长,默认为 25 秒。请将它设为短于编排器为容器提供的宽限期,否则运行时会先杀死进程,排空便失去意义。
升级
升级即对现有数据库执行 prisma migrate deploy,然后执行种子数据,最后执行 reconcile。与首次部署相同的四条命令。
这一路径是按自行托管用户实际的升级路线测试的,而不是针对全新数据库:CI 会还原一份落后 20 个迁移的快照,将其迁移到当前最新版本,验证结构,并对核心表运行冒烟检查。该滚动窗口内的任何版本都是经过验证的升级起点。迁移文件夹一经提交即不可更改;若有任何一个被重命名、重新编号、删除或修改,lint 步骤都会使构建失败,因为这四种操作都会破坏已记录旧记录的数据库上的 migrate deploy。
一次迁移失败会阻塞之后的所有部署。migrate deploy 会在第一次失败时停止,并以 P3009 拒绝之后的所有迁移,因此安装会停留在旧版本,直到有人手动清除该状态。项目自身的生产环境曾因此连续一天拒绝每次部署。清除之前,必须先确认失败的迁移确实已回滚:它创建的任何对象都不应存在,它修改过的内容应仍是原始结构;然后才能将其标记为已回滚。部分应用的迁移不能作为候选项。
仓库中没有记录自行托管 Compose 部署的备份与恢复方法,因此本页也不描述相应流程。
单机还是托管云
上文所述的单主机 Docker Compose 部署是经过验证的方式:项目自身的生产环境就是这样运行的,在一台服务器上,数据库、对象存储、API、worker、管理后台和网站均以容器形式运行,并位于共享的反向代理之后。
扩展到多台机器会改变三件事,而且这些变化是强制执行的,而不仅仅是建议。在 local 模式之外,Nessie 会把 API 和 worker 作为独立进程运行,且各自运行多个副本,因此任何假定某个进程独占机器磁盘的能力都会被拒绝:基于文件系统的对象存储(会导致启动失败)、使用 docker 提供方的执行环境模板(不起作用,且配置时会明确报错),以及 file_read、file_write 和 file_glob 内置工具(返回智能体可以读取的失败结果)。每一项拒绝都会说明相关设置、所处的模式,以及应改用什么。
对于托管云,仓库在 infrastructure/terraform/ 中提供了面向 Google Cloud 的 Terraform 配置:API 以 Cloud Run 服务的形式运行,并保持至少一个实例——它持有一个持久的 Postgres LISTEN 客户端并运行维护性扫描,因此若缩容到零,实时推送就会中断;worker 以 Cloud Run worker pool 的形式运行,因为它不绑定端口,若使用 Service 则永远无法就绪;另有一个在每次发布前把关的迁移任务、Cloud SQL for PostgreSQL 17,以及通过兼容 S3 的后端访问的 GCS 存储桶。
该路径仅有编写,尚未实际跑通。仓库对此直言不讳,本页也如实说明:尚无为其创建的 Google Cloud 项目;配置树中的任何内容都未针对真实项目进行规划或应用;通往 GCS 的分片上传路径未在真实存储桶上测试;worker pool 资源类型尚未验证;其中的可信代理跳数只是占位值,有待测量,而非测量结果。管理后台和公开网站在该配置中完全没有建模。请把它视为需要你自行验证的起点,而非受支持的部署方式。
部署数据存放在何处、软件保存什么、刻意不保存什么,另见单独的页面:欧盟制造,以及你的数据存放在哪里。