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 -vdocker 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。