首页
/ claude-mem Server(Beta):Postgres + BullMQ 可部署运行时——架构、环境校验与生产部署指南

claude-mem Server(Beta):Postgres + BullMQ 可部署运行时——架构、环境校验与生产部署指南

2026-09-06 15:28:35作者:范垣楠Rhoda

本篇基于仓库内的 server 部署文档,讲解 claude-mem 13 的 Beta 版服务器运行时:一个以 Postgres 为规范存储、以 BullMQ(Valkey)为队列、以 API Key 鉴权为边界的可部署服务。它用 claude-mem-server + claude-mem-worker 两个角色取代了旧的本地 claude-mem worker,解决的核心问题是:长时间运行的 AI 生成任务如何不阻塞 HTTP 请求、如何水平扩缩、以及重启后如何不丢任务。读完本文,你将掌握该运行时的完整环境变量契约、Compose 部署流程、API Key 生命周期管理,以及端到端验证脚本背后的设计依据。

架构总览:Postgres 规范存储 + Valkey 队列 + 双进程角色

原文档给出了如下的部署架构图:

                +-------------------+
                |  Hooks / SDK / MCP|
                |    (clients)      |
                +---------+---------+
                          |  HTTPS / Bearer API key
                          v
+-----------------+  +----+---------+   +-------------------+
|    Postgres     |<-+ claude-mem-  +-->+      Valkey       |
| (canonical      |  |   server      |   | (BullMQ queue,   |
|  storage:       |  | --daemon      |   |  noeviction,     |
|  events,        |  | HTTP only,    |   |  appendonly yes) |
|  observations,  |  | no generation |   +---------+---------+
|  jobs, sessions,|  +-------+-------+             ^
|  api_keys)      |          | enqueue              |
+--------^--------+          v                      |
         |          +-----------------+             |
         +----------+ claude-mem-     +-------------+
            read    |  worker (Nx)    |  consume jobs
            write   |  server worker  |  call provider
                    |  start          |
                    +-----------------+

各组件的职责分工如下:

  • Hooks / SDK / MCP 客户端:所有请求方(Claude Code hooks、SDK、MCP 工具)通过 HTTPS 携带 Bearer API Key 访问 HTTP 服务;
  • Postgres:规范存储(canonical storage),持久化 events、observations、jobs、sessions、api_keys 等全部领域数据;
  • Valkey:BullMQ 队列后端,配置 noeviction + appendonly yes(AOF 持久化),保证已入队的生成任务在进程重启后仍然存活;
  • claude-mem-server:纯 HTTP 运行时,只负责接收事件、写入 Postgres、向队列投递生成任务,自身不做任何 AI 生成;
  • claude-mem-worker (Nx):BullMQ 消费端,从 Valkey 轮询任务、调用模型 provider、把生成结果写回 Postgres。

HTTP 服务与生成 worker 来自同一个镜像、同一份代码库,但被拆成独立进程/容器,文档给出的理由有三条:

  1. 长时间运行的 provider 调用不会阻塞 HTTP 响应;
  2. 生成能力可以水平扩展:docker compose up -d --scale claude-mem-worker=4
  3. 重启 HTTP 服务不会丢失已入队的生成工作——任务活在 Valkey 中并由 AOF 持久化。

从源码结构看,这个拆分与文档一一对应。队列侧由 ActiveServerQueueManager.ts 管理,消费侧由 ActiveServerGenerationWorkerManager.ts 挂载 BullMQ Worker;两者监听的队列名在 jobs/types.ts 中集中定义:

export const SERVER_JOB_QUEUE_NAMES: Record<ServerGenerationJobKind, string> = {
  event: 'server_beta_generate_event',
  summary: 'server_beta_generate_summary'
};

export const SERVER_JOB_KIND_PREFIX: Record<ServerGenerationJobKind, string> = {
  event: 'evt',
  summary: 'sum'
};

即 worker 实际挂载的正是文档所称的 eventsummary 两条队列(lane),任务 ID 前缀 evt_ / sum_ 与原文档中幂等 ID 的形式 evt_<sha256> / sum_<sha256> 完全一致。

另外需要明确一点:旧的 claude-mem worker 运行时在 Docker 中不会被启动。容器入口始终运行 bun server-service.cjs --daemon(或 worker start),永远不会运行 worker-service.cjs。这在 entrypoint.sh 中可以直接得到验证:入口脚本根据 CLAUDE_MEM_CONTAINER_MODE 选择角色——server(默认)执行 server-service.cjs --daemonworkerunset CLAUDE_MEM_GENERATION_DISABLED 再执行 server-service.cjs worker start,另有 shell 模式用于进入容器做工具操作。

必需环境变量与启动期强校验

服务端在启动时运行 validateServerBetaEnv(),任何必需变量缺失或非法都会直接拒绝启动。文档给出的完整校验契约如下:

变量 必需性 说明
CLAUDE_MEM_RUNTIME Docker Docker 内必须为 server-beta(否则告警)。
CLAUDE_MEM_QUEUE_ENGINE Docker 必须为 bullmq。Docker 内拒绝进程内队列。
CLAUDE_MEM_SERVER_DATABASE_URL 始终 Postgres 连接串,启动期快速失败。
CLAUDE_MEM_REDIS_URL bullmq 队列引擎为 bullmq 时必需。
CLAUDE_MEM_AUTH_MODE 始终 Docker 内不允许为 local-dev
CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS Docker Docker 内不允许为 1/true
CLAUDE_MEM_GENERATION_DISABLED 可选 当 HTTP 服务与独立 worker 分离部署时,在 HTTP 服务上设为 true
CLAUDE_MEM_SERVER_PROVIDER Worker claudegeminiopenrouter 三选一,仅 worker 需要。
ANTHROPIC_API_KEY(或等价变量) Worker 所选 provider 的凭据。

本地开发仅在 Docker 之外可以使用 SQLite + local-dev 鉴权旁路;可部署模式必须满足上表。这套校验在仓库中有两处可执行的证据:

  • docker-compose.yml 头部注释明确列出启动校验清单,并对 POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB 使用了 ${VAR:?VAR is required} 语法——缺少任一机密,Compose 直接拒绝启动;
  • E2E 脚本 e2e-server-docker.sh 专门起一个一次性容器、注入 CLAUDE_MEM_AUTH_MODE=local-dev + CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS=1,断言进程以非零码退出且 stderr 含 local-dev is not allowed in Docker

从源码结构看,Docker 环境识别是双保险的:entrypoint 显式导出 CLAUDE_MEM_DOCKER=1,注释说明运行时同时会自动探测 /.dockerenv,用于校验器判断“是否处于容器内”。

生成 worker 模式:claude-mem server worker start

同一镜像通过下面的命令运行生成 worker 角色:

claude-mem server worker start

该进程的行为契约是:

  • 使用与 HTTP 服务相同的配置连接 Postgres 和 Valkey;
  • 将 BullMQ Worker 挂载到 eventsummary 两条队列;
  • 不打开任何 HTTP 监听端口;
  • 前台阻塞运行,适合 docker runkubectl run、systemd 等托管方式;
  • 即使继承了共享 Compose 文件里的 CLAUDE_MEM_GENERATION_DISABLED=true 也会强制开启生成——因为 worker 本身就是生成进程。

最后一条在 entrypoint.sh 中有直接实现:worker 分支在进入 server-service.cjs worker start 之前执行 unset CLAUDE_MEM_GENERATION_DISABLED

在 Compose 中,这一角色对应 claude-mem-worker 服务,可水平扩展:

docker compose up -d --scale claude-mem-worker=4

并发安全由两层保证:BullMQ 保证同一时刻只有一个 worker 处理某个任务;ProviderObservationGenerator.process 内部的 provider 调用对任务 ID(evt_<sha256> / sum_<sha256>)是幂等的,因此重试不会重复产生 observations。任务载荷结构在 jobs/types.ts 中定义:event 任务携带 agent_event_idsummary 任务携带 server_session_id,并且 Zod schema 在入队边界同步校验载荷,缺少 team_idproject_idgeneration_job_id 这类编程错误会在入队时即被拒绝,而不是生成一个 worker 无法审计的坏任务。

CLI 侧的入口在 npx-cli/commands/server.ts,帮助文本列出了完整的 server 子命令面:start, stop, restart, status, api-key create|list|revoke, keys rotate, worker start, jobs status|failed|retry|cancel——即 worker 与任务队列的日常运维都收敛在这一个 CLI 命名空间下。

生产鉴权:一次性明文、按次查库的 API Key 体系

生产部署统一使用:

CLAUDE_MEM_AUTH_MODE=api-key

API Key 通过 CLI 创建:

claude-mem server api-key create \
  --name "ci" \
  --scope memories:read,memories:write

关键设计点:

  • 明文只展示一次;Postgres 的 api_keys.key_hash 列只保存 SHA-256 哈希;
  • 撤销命令为 claude-mem server api-key revoke <id>
  • 撤销在每次请求时都生效:鉴权中间件 requirePostgresServerAuth 每次调用都按哈希回表查询该行,不存在可被污染的内存缓存。

requirePostgresServerAuth 的实现在 middleware/postgres-auth.ts 中,是 Postgres 存储模式下所有受保护路由的鉴权入口。E2E 脚本还演示了两种典型 scope 的划分:全权限 key(events:write,sessions:write,observations:read,jobs:read,memories:read,memories:write)与只读 key(observations:read,jobs:read,memories:read),撤销只读 key 后,后续读写请求应返回 401/403(见 e2e-server-docker.sh)。

原文档特别用引用块强调了一条安全边界:

不要在 Docker 内启用 CLAUDE_MEM_AUTH_MODE=local-dev loopback 旁路依赖请求来自 HTTP 监听端的 127.0.0.1,这在容器内不是有意义的边界。启动校验器会拒绝该组合并以非零退出码退出。

此外,docker-compose.yml 头部注释还说明了一套并行的计费选择:API-Key 模式下生成走计费的 provider API key(高 observation 量下成本可能显著);也可将生成 provider 指向 Claude 订阅/Pro 会话以避免按 token 计费,此时 HTTP 侧的 bearer API key 契约保持不变。

Compose 栈:四个服务与可复制的部署方式

docker-compose.yml 提供了四个服务:

  • postgrespostgres:17-alpine):规范存储;schema 在启动时由 bootstrapServerBetaPostgresSchema() 引导(这是一个幂等的进程内迁移运行器,无需外部扩展)。凭据无默认值,缺失即拒绝启动,可用 .env 文件或内联 POSTGRES_USER=... POSTGRES_PASSWORD=... docker compose up 提供;
  • valkeyvalkey/valkey:8-alpine):BullMQ 队列后端,启动命令即文档所述三条关键配置——--appendonly yes(AOF 持久化)、--appendfsync everysec--maxmemory-policy noeviction(BullMQ 硬性要求);
  • claude-mem-server:HTTP 运行时,显式设置 CLAUDE_MEM_GENERATION_DISABLED=true,因此该容器内不挂载 BullMQ Worker;监听 0.0.0.0:37877,健康检查为 curl http://127.0.0.1:37877/healthz
  • claude-mem-worker:生成 worker,水平扩展单位;provider 配置全部集中在它上面(CLAUDE_MEM_SERVER_PROVIDER 默认 claude,以及 ANTHROPIC_API_KEY / GEMINI_API_KEY / OPENROUTER_API_KEY)。

启动与销毁:

docker compose up -d --build
docker compose down -v

-v 会连同数据卷一起清除,等价于抹掉全部数据。)

文件头部注释还给出了几条值得注意的运维约定:

  • 该文件不可未经修改部署到任何公网可达环境(包括可做横向移动的 VPN 后 staging),因为 Postgres 凭据以必需环境变量方式提供,需自行改为机密管理;
  • 所有长驻服务声明 restart: unless-stopped,崩溃容器(OOM、panic、瞬时依赖故障)会自动拉起;
  • CLAUDE_MEM_REDIS_URL${CLAUDE_MEM_REDIS_URL:-redis://valkey:6379} 回退默认,可指向外部 Redis;
  • 文件内预留了注释掉的 CREDENTIALS_FILE 挂载块,支持把 provider/API 机密以只读文件形式挂进容器,而不内联到环境变量里;
  • server 与 worker 均声明了 postgres-data / valkey-data / claude-mem-data 三个命名卷的归属,claude-mem-data 挂载到 /data/claude-mem

角色选择的开关是 CLAUDE_MEM_CONTAINER_MODEserver(默认,server-service.cjs --daemon)、worker(生成 worker)、shell(进入容器执行工具),完整分支逻辑见 entrypoint.sh

端到端测试:可执行的行为契约

scripts/e2e-server-docker.sh 拉起完整栈并做全量断言,任何断言失败都会以非零码退出。它验证的内容与原文档一致,且脚本注释补充了细节:

  1. POST /v1/events?wait=true 经由队列完整生成一条 observation;
  2. 流程中途 docker compose restart claude-mem-server claude-mem-worker 后,任务与 observation 不丢失(队列持久性验证);
  3. 撤销 API Key 后,后续读/写被拒绝(401/403);
  4. 任何容器内都没有 worker-service.cjs 进程运行——脚本通过 pgrep -af 'worker-service\.cjs' 在两个容器内分别检查;
  5. Docker 内 CLAUDE_MEM_AUTH_MODE=local-dev 被拒绝(含对错误信息的精确匹配)。

测试主体逻辑在 docker/e2e/server-e2e.mjs,以独立的 server-e2e Compose 服务运行,分 phase1(功能路径:事件提交、生成、查询)与 phase2(重启后的持久化与已撤销 key 检查)两个阶段注入环境变量执行。API Key 的创建与撤销则直接 exec 进 server 容器,用 bun server-service.cjs server api-key create|revoke 完成——注释特别指出这条子树由 Postgres 支撑,而非旧的 SQLite worker-service 树。

适用前提与小结

适用前提需要说清楚:这是 Beta 运行时,面向可部署(deployable)场景;本地开发仍应走 SQLite + local-dev 旁路的轻量路径(仅限 Docker 之外)。可部署模式的最小可行配置是——Postgres 17 与 Valkey 8(或兼容 Redis)实例、CLAUDE_MEM_RUNTIME=server-betaCLAUDE_MEM_QUEUE_ENGINE=bullmqCLAUDE_MEM_AUTH_MODE=api-key,外加 server/worker 各自所需的 CLAUDE_MEM_SERVER_DATABASE_URLCLAUDE_MEM_REDIS_URL 与 worker 侧的 provider 凭据。

整体设计上,这一运行时值得参考的取舍有三点:用“同镜像双角色”把 AI 生成从 HTTP 关键路径中剥离,换来延迟隔离与独立扩缩;把任务状态外置到 AOF 持久化的 Valkey,使“重启不丢任务”成为可被 E2E 断言验证的性质;用“明文一次展示 + 每次请求按哈希查库”把 API Key 撤销做成无缓存、无宽限期的强一致行为。这三者分别对应文档中架构图、Compose 拆分和鉴权三个小节,也都能在仓库的 entrypointCompose 定义队列类型鉴权中间件 中找到一一对应的实现证据。

登录后查看全文
热门项目推荐
相关项目推荐