SSD Nodes Learn 🎉 VPS $4.99/月起
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-07

VPS 自托管 mem0 需要多少内存?

了解 mem0 三容器的实际内存和磁盘成本:稳定运行约需 1 GB,2 GB VPS 可用;同机运行 Ollama 的 8B 4 bit 模型约需 6 GB 内存,总配置至少 8 GB。

自行在 VPS 上托管 mem0 的实际内存成本

自行托管 mem0 需要运行 3 个容器:FastAPI memory server、带 pgvector 扩展的 Postgres,以及 Next.js dashboard。mem0 是智能体的记忆层。您将对话发送给它,语言模型会从对话中提取持久事实,并将这些事实存储为向量,以便后续查询检索相关内容。

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 上,内核的 out-of-memory killer 会终止该进程,构建最终显示 exit code 137。在排查 Docker bug 前,请先确认原因:

dmesg -T | grep -i "killed process"

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

是否需要 Neo4j 才能使用 mem0 图记忆?

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

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

移除图存储后,可以省去一个 JVM 容器、其堆内存,以及数百 MB 的镜像空间。在 2 GB VPS 上,这可能决定服务是正常运行还是开始使用 swap。

下面明确说明您会失去什么。以前,搜索结果会包含 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 的文件监视器。它用于在编辑代码时重启进程;在生产环境中,它只会占用内存并额外运行一个进程,不会带来实际作用。生产环境的 DockerfileCMD 中也包含 --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_PASSWORDJWT_SECRETADMIN_API_KEY。保留 AUTH_DISABLED=false。该名称准确说明了这个标志的作用:启用后,服务器会将其持有的全部内存提供给任何能够访问该端口的人。如果不希望将 onboarding 事件发送到上游,请设置 MEM0_TELEMETRY=false

服务器会使用 secrets.compare_digestADMIN_API_KEYX-API-Key 标头进行比较。匹配后会跳过所有数据库查询。这是整个 API 的 root 凭据。应按 root 凭据处理:不要写入 shell 历史记录,不要提交到 git,也不要将其粘贴到提示符中。Compose 环境文件以及密钥如何从其中泄露避免 API 密钥进入代理上下文 都直接适用,因为此服务器的调用方是代理。

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

将 TLS 放在 API 前面,而不是开放 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 时,语言模型会读取对话并提取事实,因此该调用会一直阻塞。在 CPU 上运行本地 8B 模型时,处理时间经常超过 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 事件。UPDATEDELETE 事件已被移除,因此缺少这些事件不是错误。

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 验证这些接口正常后,就可以将相同的接口接入代理,无论是直接接入,还是通过运行在同一 VPS 上的 MCP 服务器接入。

完全不使用 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 调用都需要数秒,并且存储的每条消息都要承担这部分开销。如果延迟很重要,使用附带 GPU 的 VPS 才是合理的解决方案。相比预期,给 8B 模型增加更多 CPU 核心的帮助要小得多。

无论选择哪种方案,都必须遵守一条规则:不要在同一个集合中混用嵌入模型。两个宽度恰好相同但实际不同的模型生成的向量不可比较。插入会成功,搜索也会返回行,但这些行是错误的,而且系统不会在任何位置报告错误。

备份:有两个数据库,而不是一个

最常见的 mem0 备份错误是只导出一个数据库。init-db.sh 会在默认的 postgres 数据库旁创建 mem0_app,两者存储的内容不同。postgres 数据库存储 pgvector 集合,也就是记忆数据。mem0_app 存储用户、会话、API 密钥和请求日志。

只恢复 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 会在卷名称前加上项目名称,因此在假定 mem0_mem0_history 之前,先使用 docker volume ls 确认实际名称。

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

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"。请将指向 Ollama 的 openai_base_url"provider": "openai" 配合使用。

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

模型变更后,搜索返回的行完全不合理,但其他位置没有任何错误。维度仍然匹配,因此数据库不会报错;但两个模型会将同一句话映射到不同的位置。请创建新集合,然后重新添加数据。

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

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

执行 docker compose up --build 时出现 exit code 137,表示 out-of-memory killer 正在终止 dashboard 构建。请添加 swap,或在更大规格的机器上构建镜像,然后将其推送到 registry。

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

FAQ

运行带图记忆的 mem0 后,仍需要 Neo4j 吗?

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

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

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

不使用 OpenAI API key,可以运行 mem0 吗?

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

切换到本地 embedding 模型后,为什么 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