SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-28

VPS自托管mem0需要多少内存?配置与本地部署

了解VPS运行mem0的真实资源需求:3个容器约占1 GB常驻内存,2 GB VPS可运行;同机使用Ollama和4 bit 8B模型至少需要8 GB,并提供Compose、localhost绑定与TLS配置。

在 VPS 上自行托管 mem0 实际需要多少 RAM

自行托管 mem0 意味着运行 3 个容器:FastAPI memory server、带有 pgvector 扩展的 Postgres,以及 Next.js dashboard。mem0 是 agent 的记忆层。您将对话提交给它,语言模型从对话中提取持久事实,再将这些事实存储为向量,以便后续查询检索相关事实。

为这 3 个容器预留约 1 GB 常驻内存,并在镜像构建完成后预留 3 到 4 GB 磁盘空间。当语言模型运行在其他位置时,2 GB VPS 可以稳定运行这套服务。如果模型通过 Ollama 在同一台服务器上运行,模型的资源占用会远超其他组件:一个量化为 4 bit 的 8B 模型自身约需要 6 GB,因此完整的本地部署至少需要 8 GB。

不要直接采用博客文章中的这些数据,包括本文的数据。请测量您实际构建的服务栈。

docker compose ps
docker stats --no-stream
docker system df -v

docker stats 输出每个容器的常驻内存。docker system df -v 输出每个镜像和每个卷占用的磁盘空间。

稳定状态的资源占用不等于峰值。docker compose up -d --build 会编译 Next.js dashboard,该 Node 构建过程是整个安装过程中最耗内存的阶段。在 1 GB VPS 上,内核的内存不足终止程序,构建以 exit code 137 结束。请先确认原因,再排查 Docker bug:

dmesg -T | grep -i "killed process"

如果对于您的需求而言,服务器包含的组件过多,可以选择更小的方案。完全不需要服务器的本地 agent 记忆存储和直接运行在 Claude Code 中的记忆功能都不需要数据库。当多个 agent 或多台机器需要读取相同记忆时,再回到这里。

mem0 的图记忆需要 Neo4j 吗?

不需要。如果某个指南要求您添加 Neo4j 容器,说明该指南早于当前代码。

mem0 中的图记忆过去指外部图数据库。它通过 graph_store 配置,并将 enable_graph 设置为 true。2026 年 4 月发布的新记忆算法从开源 SDK 中移除了这两个键。实体提取现在在普通的 add 流程中执行,实体会写入第二个 pgvector 集合。该集合以主集合名称命名,并追加 _entities。无需执行迁移。内置实体链接会在下一次 add 调用时开始生效。

移除图存储后,可以省去一个 JVM 容器、相应的堆内存,以及数百 MB 的镜像空间。对于 2 GB VPS,这可能决定系统是正常运行还是开始使用交换空间。

需要放弃的功能如下。过去,搜索结果会包含 relations 字段,用于列出实体之间的边。该字段已经移除。现在,实体匹配会提高记忆在综合评分中的位置,但不存在可遍历的结构。如果您的应用曾遍历这些关系,mem0 现在不会再保存它们。您需要在 mem0 之外自行维护图数据库,并通过自己的代码向其中写入数据。

仓库中的 compose 文件用于开发环境

server/docker-compose.yaml 声明了 name: mem0-dev,实际行为也确实如此。运行前先阅读该文件,因为其中有五处配置不适合服务器。

  • 它从 server/dev.Dockerfile 构建镜像,并使用 .:/app 将工作副本挂载到镜像中。因此,容器运行的是该目录中的内容,而不是你构建到镜像中的内容。
  • 它的命令是 rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload。该命令会在每次启动时从 PyPI 重新安装 mem0ai。因此,即使你只是重启服务,服务器运行的版本也可能发生变化,而你并未将这次操作视为升级。
  • 同一个 pip 步骤还意味着:如果出站网络不可用,服务会在 uvicorn 启动前失败。此时内存服务器会因为无法访问 PyPI 而停止运行。
  • --reload 会启动 uvicorn 的文件监视器。它用于在你编辑代码时重启进程;在生产环境中,它只会增加内存占用并运行一个无用的额外进程。生产环境的 Dockerfile 也在其 CMD 中包含 --reload,因此无论哪种情况都需要覆盖该命令。
  • 发布的端口是 "8888:8000"、"8432:5432" 和 "3000:3000"。发布端口前未指定地址时,会绑定到 0.0.0.0。因此,堆栈一启动,Postgres 就会通过 8432 端口响应公网请求。

最后一点需要单独警告。Docker 会将自己的规则写入 ufw 管理的规则链之前,因此 ufw deny 8432 无法关闭已发布的容器端口。Docker 绕过 ufw 直接发布端口详细介绍了相关规则。

面向生产服务器的 Compose 文件

在 server/ 中操作,保留原有的 init-db.sh,并将 docker-compose.yaml 替换为以下内容。

name: mem0

services:
  mem0:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:8888:8000"
    networks: [mem0_network]
    volumes:
      - mem0_history:/app/history
    depends_on:
      postgres:
        condition: service_healthy
    command: >
      sh -c "alembic upgrade head &&
             uvicorn main:app --host 0.0.0.0 --port 8000"
    environment:
      - PYTHONUNBUFFERED=1
      - DASHBOARD_URL=https://mem0.example.com
      - APP_DB_NAME=mem0_app
      - AUTH_DISABLED=false
      - MEM0_TELEMETRY=false

  postgres:
    image: pgvector/pgvector:pg17
    restart: unless-stopped
    shm_size: "128mb"
    networks: [mem0_network]
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -q -U ${POSTGRES_USER:-postgres}"]
      interval: 5s
      timeout: 5s
      retries: 5
    volumes:
      - postgres_db:/var/lib/postgresql/data
      - ./init-db.sh:/docker-entrypoint-initdb.d/init-db.sh

  mem0-dashboard:
    build: ./dashboard
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    networks: [mem0_network]
    environment:
      - NEXT_PUBLIC_API_URL=https://mem0.example.com
      - API_INTERNAL_URL=http://mem0:8000
    depends_on:
      mem0:
        condition: service_started

volumes:
  postgres_db:
  mem0_history:

networks:
  mem0_network:
    driver: bridge

这里有 5 处重要修改,每一处都有明确原因。

每个 ports 条目都以 127.0.0.1 开头,因此内核只接受来自本机的连接。所有外部请求都通过反向代理进入,只有反向代理持有证书。

Postgres 完全没有 ports 配置块。mem0 容器通过 mem0_network 按服务名访问它,因此发布 8432 端口没有任何收益,反而会多开放一个端口。需要 shell 时,使用 docker compose exec postgres psql -U postgres。

数据存储从 ./history 绑定挂载改为命名卷。绑定挂载会将数据绑定到本机上的一个路径和一个 uid,而命名卷是 Docker 可以创建快照并迁移的对象。命名卷与绑定挂载的对比介绍了两者分别适用的场景。

该命令删除 --reload,保留 alembic upgrade head。请保留此迁移步骤。没有这一步,应用会连接到一个没有数据表的数据库,并且每个请求都会在第一次查询时失败。

NEXT_PUBLIC_API_URL 是浏览器访问的 URL,因此必须是公开的 HTTPS 地址,而不能是 http://mem0:8000。Next.js 会在构建时内联所有 NEXT_PUBLIC_ 值,因此修改它需要执行 docker compose up -d --build mem0-dashboard。仅重启服务会继续使用已经写入 JavaScript 的旧值,导致控制面板请求错误的主机。

密钥保存在 .env 中,而 .env 不应暴露到互联网

cd server
cp .env.example .env
openssl rand -hex 32    # paste into JWT_SECRET
openssl rand -hex 32    # paste into ADMIN_API_KEY
chmod 600 .env

设置 POSTGRES_PASSWORD、JWT_SECRET 和 ADMIN_API_KEY。保留 AUTH_DISABLED=false。该名称准确说明了此标志的作用:启用后,服务器会将其持有的全部内存提供给任何能够访问该端口的人。如果不希望将 onboarding 事件发送到上游,请设置 MEM0_TELEMETRY=false。

ADMIN_API_KEY 会通过 secrets.compare_digest 与 X-API-Key 标头进行比较。匹配时会跳过所有数据库查询。它是整个 API 的 root 凭据。必须按 root 凭据的级别保护它:不要让它进入 shell 历史记录,不要提交到 git,也不要将其粘贴到提示符中。Compose 环境文件以及其中的密钥泄露位置和避免 API 密钥进入代理上下文都直接适用,因为该服务器的调用方是代理。

从 env_file 加载的值会位于容器环境中,而 docker inspect 会完整打印这些值。任何属于 docker 组的用户都能读取这些值,任何属于 docker 组的用户实际上都拥有主机上的 root 权限。

在 API 前置 TLS,而不是开放 8888

API 在 127.0.0.1:8888 上响应,控制面板在 127.0.0.1:3000 上响应。nginx 在 443 上终止 TLS(传输层安全),并将请求转发到这两个服务。

server {
    listen 443 ssl;
    server_name mem0.example.com;

    ssl_certificate     /etc/letsencrypt/live/mem0.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mem0.example.com/privkey.pem;

    location ~ ^/(memories|search|configure|auth|api-keys|docs|openapi.json) {
        proxy_pass http://127.0.0.1:8888;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 180s;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

proxy_read_timeout 的影响比表面上更大。调用 add 时,进程会等待语言模型读取对话并提取事实。本地 8B 模型在 CPU 上运行时,经常超过 nginx 默认的 60 秒超时时间。此时,即使模型仍在处理且记忆仍会写入,调用方也会看到 504 Gateway Time-out。最终,系统实际创建了记忆,但返回结果却告诉您操作失败。

使用 默认拒绝的 ufw 策略关闭其余端口,仅保留 22 和 443。通过 在 nginx 后的 Ubuntu 24.04 上使用 certbot签发证书。如果该服务器已经使用 Traefik 路由多个 Compose 应用为其他应用提供入口,请将 mem0 添加到现有路由器中,而不是再安装一个代理。

冒烟测试:添加一条记忆并读回

export MEM0_KEY='<the ADMIN_API_KEY from .env>'

curl -sS -X POST http://127.0.0.1:8888/memories \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"messages":[{"role":"user","content":"I deploy with Docker Compose and I run Postgres 17."}],"user_id":"smoke"}'

正常响应是一个 JSON 对象,其中包含一个 results 列表。每个条目包含 id、提取出的 memory 文本和 "event": "ADD"。当前算法只返回 ADD 事件。UPDATE 和 DELETE 事件已移除,因此它们缺失不是错误。

curl -sS -X POST http://127.0.0.1:8888/search \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d '{"query":"which database do I run?","filters":{"user_id":"smoke"},"top_k":5}'

关于 Postgres 17 的事实应随分数一同返回。请按示例所示,将标识符放入 filters。顶层 user_id 仍然有效,并且服务器每次使用它时都会记录 Top-level user_id in /search is deprecated. Use filters={...} instead.。

测试完成后清理数据,避免测试数据影响实际搜索:

curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
  -H "X-API-Key: $MEM0_KEY"

如果搜索返回的行数少于预期,请先检查默认值,不要立即归咎于检索逻辑。在当前版本中,top_k 默认值为 20,从 100 下调;threshold 默认值为 0.1,而不是 none,因此系统现在会自动过滤匹配度较低的结果。通过 curl 验证成功后,就可以将相同的端点接入 agent,无论是直接接入,还是通过运行在同一 VPS 上的 MCP server接入。

完全不使用 OpenAI 密钥运行 mem0

先处理阻塞问题,因为你会在前 5 分钟内遇到它。服务器镜像只包含固定的一组 provider 库,而 /configure 会拒绝不在其中的任何内容:

LLM provider 'ollama' is not bundled in this image. Bundled providers: openai, anthropic, gemini. To use another provider, install its Python package, rebuild the container, and extend BUNDLED_LLM_PROVIDERS in server/main.py.

你不需要重新构建任何内容。Ollama 在 /v1 提供兼容 OpenAI 的 API,支持 /v1/chat/completions 和 /v1/embeddings;mem0 的 openai provider 接受 openai_base_url。将该密钥指向 Ollama 后,内置检查即可通过,因为该 provider 实际上是 openai。只有地址发生变化。

将 Ollama 添加到同一个 Compose 项目:

  ollama:
    image: ollama/ollama
    restart: unless-stopped
    networks: [mem0_network]
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_models:/root/.ollama

在顶级 volumes: 键下添加 ollama_models:,然后拉取一个聊天模型和一个嵌入模型:

docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-text

如果 Ollama 已作为 systemd 单元运行在主机上,例如直接在 VPS 上运行 Ollama,不要将容器指向 127.0.0.1:11434。在 mem0 容器内部,127.0.0.1 就是 mem0 容器。为 mem0 服务设置 extra_hosts: ["host.docker.internal:host-gateway"],在 systemd drop-in 中设置 Environment="OLLAMA_HOST=0.0.0.0:11434",使 Ollama 监听 bridge 可访问的地址,并在防火墙中继续关闭 11434。

配置前先询问模型的嵌入维度

这一步决定检索是否能够正常工作。

mem0 的 pgvector 存储会使用固定向量宽度 vector vector(1536) 创建表,因为 embedding_model_dims 默认为 1536,也就是 OpenAI text-embedding-3-small 的宽度。nomic-embed-text 会返回 768 个值。mem0 内部不会比较这两个数字,因此不匹配会在第一次插入时由 Postgres 报出:

expected 1536 dimensions, not 768

也不要直接相信本段中的数字。请询问模型:

curl -sS http://127.0.0.1:11434/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"model":"nomic-embed-text","input":"dimension check"}' \
  | python3 -c "import json,sys; print(len(json.load(sys.stdin)['data'][0]['embedding']))"

该命令会输出集合必须使用的宽度。将配置写入文件,因为通过 shell 引号传递 Postgres 密码很容易把拼写错误带入生产环境。

{
  "vector_store": {
    "provider": "pgvector",
    "config": {
      "host": "postgres",
      "port": 5432,
      "dbname": "postgres",
      "user": "postgres",
      "password": "<POSTGRES_PASSWORD from .env>",
      "collection_name": "memories_local_768",
      "embedding_model_dims": 768
    }
  },
  "llm": {
    "provider": "openai",
    "config": {
      "model": "llama3.1:8b",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1",
      "temperature": 0.2
    }
  },
  "embedder": {
    "provider": "openai",
    "config": {
      "model": "nomic-embed-text",
      "api_key": "ollama",
      "openai_base_url": "http://ollama:11434/v1"
    }
  }
}
curl -sS -X POST http://127.0.0.1:8888/configure \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MEM0_KEY" \
  -d @config.json

curl -sS http://127.0.0.1:8888/configure -H "X-API-Key: $MEM0_KEY"

第二次调用会读回配置,这是确认写入成功的检查。然后再次执行上面的冒烟测试。

该 JSON 中有 4 个细节并不明显,任何一个出错都会导致问题。

api_key 是字符串 ollama,Ollama 会忽略其值。它不能留空,因为未设置密钥时,OpenAI 客户端库会在请求离开进程前直接抛出错误。任何非空字符串都可以使用。

embedding_model_dims 应设置在向量存储中,embedder 中则有意不设置 embedding_dims。只有设置 embedding_dims 时,mem0 才会发送 OpenAI 的 dimensions 参数;不实现 Matryoshka 截断的后端会直接拒绝该参数。创建表时设置宽度,embedder 保持不变。

collection_name 是新增配置。mem0 使用 CREATE TABLE IF NOT EXISTS 创建表,因此向已有集合指定不同宽度完全不会生效:旧的 vector(1536) 列仍然存在,每次插入都会失败。更改宽度需要使用新的集合名称,或者手动删除旧表。

openai_base_url 中的主机名是 Compose 服务名称 ollama,不是 localhost。容器会通过共享网络中的服务名称解析彼此。

完全本地化方案的代价

请如实评估质量。mem0 发布的基准分数是在使用前沿模型执行信息提取时测得的,因此应将其视为上限,而不是 VPS 上 8B 模型的预期表现。小模型生成的事实更模糊,有时还会在要求返回 JSON 时输出普通文本,表现为 add 调用返回空的 results 列表且没有错误。

速度是另一项代价。仅使用 CPU 执行提取时,每次 add 调用都需要数秒,并且每条存储的消息都会产生这项开销。如果模型超出要求的 JSON 继续输出内容,耗时会更长,因此使用 num_predict 限制回复可以限制单次 add 调用的最长运行时间。如果该延迟不可接受,为 VPS 配置 GPU才是可靠的解决方案。与预期相比,增加 CPU 核心对 8B 模型的帮助小得多。更换模型比更换机器成本低;在 VPS 上运行 Nemotron 3.5 Lightning会提供需要拉取的标签、所需 RAM,以及仅使用 CPU 时是否足够快等信息。

无论选择哪种方案,都必须遵守一条规则:一个集合中不能混用嵌入模型。即使两个不同模型的宽度恰好相同,它们生成的向量也不可比较。插入会成功,搜索也会返回行,但结果是错误的,且不会有任何地方报告错误。

备份:这里有 2 个数据库,而不是 1 个

最常见的 mem0 备份错误是只导出一个数据库。init-db.sh 会在默认的 postgres 数据库旁创建 mem0_app,两者存储的内容不同。postgres 数据库存储 pgvector 集合,也就是记忆数据。mem0_app 存储用户、会话、API 密钥和请求日志。每个自托管应用对状态的拆分方式都不同,因此即使 两个执行相同任务的照片服务器,也需要使用不同的备份命令;在信任转储文件前,请先确认应用实际存储了哪些数据。这个范围的另一端,是类似于 重建为 90 年代录像店的 Jellyfin 媒体库 的系统:它从其他服务读取完整目录,因此通常只需复制自身配置;而 mem0 必须同时备份两个数据库,否则恢复结果没有用。

只恢复 postgres 时,记忆数据会恢复,但所有账户和 API 密钥都会丢失,因此无法通过身份验证来读取这些数据。使用一条命令同时导出两个数据库以及角色:

docker compose exec -T postgres pg_dumpall -U postgres --clean \
  | gzip > "mem0-$(date +%F).sql.gz"

history 卷独立于 Postgres,需要单独复制:

docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
  alpine tar czf /backup/mem0-history.tgz -C /data .

Docker 会在卷名称前添加项目名称,因此请使用 docker volume ls 确认卷名称,不要直接假定是 mem0_mem0_history。

将备份恢复到临时容器中,并在确认备份有效前检查行数:

gunzip -c mem0-2026-08-03.sql.gz \
  | docker compose exec -T postgres psql -U postgres -d postgres

从未执行过恢复的备份只是猜测。确认导出结果无误后,使用 restic 将快照发送到异地存储,因为存放在受保护服务器上的备份无法在该服务器故障时提供保护。

故障模式及实际显示的字符串

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} 表示请求头缺失或拼写错误。名称是 X-API-Key,而 curl 会按原样发送请求头名称。

在添加记忆时出现 {"detail":"At least one identifier (user_id, agent_id, run_id) is required."},表示请求未包含任何此类字段。记忆必须限定到某个范围,因为搜索会严格按这些字段进行筛选。

HTTP 400 和 LLM provider 'ollama' is not bundled in this image 同时出现,表示发送了 "provider": "ollama"。使用 "provider": "openai",并将 openai_base_url 指向 Ollama。

Postgres 返回 expected 1536 dimensions, not 768,表示集合创建时使用了一种向量维度,而嵌入模型返回了另一种维度。在向量存储上设置 embedding_model_dims,并使用新的 collection_name。

模型更换后,搜索返回的行没有意义,但任何位置都没有错误。向量维度仍然匹配,因此数据库不会报错;但两个模型会将同一句话放在不同的位置。创建新集合并重新添加数据。

mem0 日志中出现 Connection refused,且正在连接 Ollama,通常表示 openai_base_url 中的 127.0.0.1 配置有误。在容器内,该地址指向容器自身。请使用服务名称;如果 Ollama 运行在宿主机上,则使用宿主机网关。

添加记忆时 nginx 返回 504 Gateway Time-out,表示模型处理时间超过了 proxy_read_timeout。提高该值,并在重试请求前确认记忆是否已经写入。

执行 docker compose up --build 时出现 exit code 137,表示内存不足终止程序停止了 dashboard 构建。添加 swap,或在配置更大内存的机器上构建镜像,再将其推送到 registry。

error: port 3000 is already in use 来自仓库的 make up 目标;当 3000 或 8888 已被占用时,该目标会拒绝启动。使用 lsof -iTCP:3000 -sTCP:LISTEN 查找占用端口的进程。

FAQ

我还需要 Neo4j 才能让 mem0 使用图记忆吗?

不需要。2026 年 4 月发布的新记忆算法已从开源 SDK 中移除 graph_store 和 enable_graph 配置键。实体提取现在会在正常添加记忆时运行,并将结果写入名为 <collection_name>_entities 的第二个 pgvector 集合。因此不再需要外部图数据库、额外容器或迁移步骤。代价是,搜索结果中的 relations 字段不再存在。实体现在用于提升记忆的排名,而不是提供可遍历的边。因此,依赖遍历这些关系的应用需要在 mem0 外部使用自己的图存储。

运行自托管 mem0 服务器所需的最小 VPS 配置是什么?

如果语言模型托管在其他位置,2 GB 内存和约 4 GB 可用磁盘空间就足以运行 API 容器、Postgres 和控制面板。资源压力最大的是首次构建,因为编译 Next.js 控制面板比运行它占用更多内存。1 GB 的服务器会因 exit code 137 而终止构建。如果 Ollama 在同一台服务器上运行,应根据模型配置资源:4-bit 量化的 8B 模型本身大约需要 6 GB,因此建议使用 8 GB 内存。

不使用 OpenAI API 密钥也能运行 mem0 吗?

可以,通过 Ollama 的 OpenAI 兼容端点运行。设置 "provider": "ollama" 会失败,因为服务器镜像只包含 openai、anthropic 和 gemini 库,并返回 HTTP 400。应保留 "provider": "openai",并为 llm 和 embedder 设置 "openai_base_url": "http://ollama:11434/v1",其值可以是任意非空的 api_key。Ollama 会忽略该密钥,而内置的提供商检查也会通过,因为实际使用的提供商是 openai。

为什么切换到本地嵌入模型后,mem0 不返回结果?

因为 pgvector 表是在固定维度下创建的。embedding_model_dims 的默认值为 1536,而 nomic-embed-text 返回 768,Postgres 会因 expected 1536 dimensions, not 768 拒绝插入。mem0 使用 CREATE TABLE IF NOT EXISTS 创建表,因此仅修改维度不会影响现有集合。将 embedding_model_dims 设置为模型的实际维度,然后调用 /v1/embeddings 并统计其返回的值数量来确认该维度。同时为向量存储设置新的 collection_name。