自行託管 mem0 需要多少記憶體?VPS 部署指南
了解 mem0 在 VPS 上的實際資源需求:3 個容器約需 1 GB RAM,2 GB VPS 可執行;同機使用 Ollama 與 8B 模型則至少需要 8 GB。
自行在 VPS 上託管 mem0 的實際記憶體成本
自行託管 mem0 代表執行 3 個容器:FastAPI 記憶體伺服器、含 pgvector 擴充功能的 Postgres,以及 Next.js 儀表板。mem0 是代理程式的記憶體層。您將對話傳送給它,由語言模型從對話中擷取持久資訊,再將這些資訊儲存為向量,之後的查詢即可取回相關內容。
3 個容器約需預留 1 GB 的常駐記憶體。映像檔建置完成後,磁碟空間約需 3 到 4 GB。當語言模型在其他位置執行時,2 GB 的 VPS 可穩定執行這個配置。若透過 Ollama 在同一台主機上執行模型,模型的資源需求會遠高於其他元件:採用 4 位元量化的 8B 模型本身約需 6 GB,因此完整的本機配置至少需要 8 GB。
不要直接採用部落格文章中的數據,包括本文。請測量您實際建置的堆疊。
docker compose ps
docker stats --no-stream
docker system df -vdocker stats 會列出每個容器的常駐記憶體。docker system df -v 會列出每個映像檔與每個 volume 所佔用的磁碟空間。
穩定狀態的用量不代表峰值。docker compose up -d --build 會編譯 Next.js 儀表板,而這個 Node 建置程序是整個安裝過程中最耗用記憶體的階段。在 1 GB 的 VPS 上,核心的 out-of-memory killer 會將其終止,建置最後會顯示 exit code 137。在排查 Docker 錯誤前,請先確認原因:
dmesg -T | grep -i "killed process"如果您需要的功能不多,這套架構可能過於複雜;較小的方案確實可行。完全不需要伺服器的本機代理程式記憶體儲存和直接存在 Claude Code 內部的記憶體都不需要資料庫。等到有多個代理程式或多台機器需要讀取相同記憶體時,再回到這裡。
我需要為 mem0 的圖形記憶體部署 Neo4j 嗎?
不需要。如果指南要求你新增 Neo4j 容器,表示該指南早於目前的程式碼。
mem0 過去所稱的圖形記憶體,是指外部圖形資料庫。它會在 graph_store 鍵下設定,並將 enable_graph 設為 true。2026 年 4 月發布的新記憶體演算法,已從開放原始碼 SDK 移除這兩個鍵。現在,實體擷取會在一般的 add 流程中執行,並將實體寫入第二個 pgvector collection。該 collection 的名稱會以主要 collection 名稱加上 _entities 組成。不需要執行資料遷移。內建的實體連結會在下一次呼叫 add 時開始運作。
移除圖形儲存庫後,不再需要 JVM 容器及其 heap,也能省下數百 MB 的 image 空間。對 2 GB VPS 而言,這可能決定服務能否正常執行,而不是開始使用 swap。
以下直接說明你會失去的功能。過去的搜尋結果會包含 relations 欄位,列出實體之間的邊。現在已移除該欄位。實體相符項目現在只會提高記憶體在合併分數中的位置,而且沒有可供遍歷的結構。如果你的應用程式會遍歷這些關係,mem0 現在不再保存它們。你必須在 mem0 外部自行維護圖形資料庫,並由自己的程式提供資料。
儲存庫中的 compose 檔案是開發環境用的 compose 設定
server/docker-compose.yaml 宣告了 name: mem0-dev,而且確實會執行該設定。執行前請先閱讀,因為其中有 5 項設定不適合伺服器。
- 它從
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,因此 stack 一啟動,Postgres 就會透過 8432 回應公用網際網路。
最後一點需要另外提醒。Docker 會在 ufw 管理的 chain 前方加入自己的規則來發布連接埠,因此 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 開頭,因此 kernel 只接受來自本機的連線。所有外部請求都會經過 reverse proxy,而憑證只由 reverse proxy 管理。
Postgres 完全沒有 ports 區塊。mem0 container 會透過 mem0_network,以 service name 連線至 Postgres,因此發布 8432 沒有任何好處,反而會多開一個連接埠。需要 shell 時,請使用 docker compose exec postgres psql -U postgres。
歷史上使用的是 ./history bind mount,現在改用 named volume。bind mount 會將資料綁定至此主機上的單一路徑與單一 uid;named volume 則是 Docker 可以建立 snapshot 並移動的物件。Named volume 與 bind mount 的比較說明兩者各自適用的情況。
這個指令移除 --reload,並保留 alembic upgrade head。請保留這個 migration 步驟。若沒有這個步驟,應用程式會對沒有資料表的資料庫啟動,導致每個請求在第一次查詢時失敗。
NEXT_PUBLIC_API_URL 是瀏覽器呼叫的 URL,因此必須是公開的 HTTPS 位址,而不是 http://mem0:8000。Next.js 會在建置時將每個 NEXT_PUBLIC_ 值內嵌,因此變更該值時需要 docker compose up -d --build mem0-dashboard。單純重新啟動會保留內嵌在 JavaScript 中的舊值,導致 dashboard 呼叫錯誤的主機。
Secrets 存放在 .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。
系統會使用 secrets.compare_digest 將 ADMIN_API_KEY 與 X-API-Key 標頭比較;比對成功時,會略過所有資料庫查詢。這是整個 API 的 root 憑證。請以同等級別的方式保護它:不要讓它出現在 shell 歷程記錄或 git 中,也不要將它貼到提示中。Compose 環境檔案,以及其中的 secret 如何外洩 和 避免 API 金鑰進入 agent 的內容 都直接適用,因為此伺服器的呼叫端是 agent。
從 env_file 載入的值會存放在容器環境中,而 docker inspect 會完整輸出這些值。docker 群組中的任何人都能讀取它們,而 docker 群組中的任何人,實際上都擁有主機上的 root 權限。
在 API 前方設定 TLS,而不是直接開放 8888
API 在 127.0.0.1:8888 回應,dashboard 在 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 呼叫時,系統會等待 language model 讀取對話並擷取事實。以 CPU 執行本機 8B model 時,處理時間通常會超過 nginx 預設的 60 秒。此時呼叫端會收到 504 Gateway Time-out,但 model 仍在處理,記憶仍會寫入。結果是系統建立了記憶,但回報給你的結果卻是失敗。
其餘連接埠則套用 預設拒絕的 ufw 政策,只開放 22 和 443。若要簽發憑證,請使用 在 nginx 後方的 Ubuntu 24.04 上執行 certbot。如果該主機已使用 Traefik 路由多個 Compose 應用程式 對外提供其他應用程式,請將 mem0 加入現有的 router,而不要再安裝第二個 proxy。
Smoke test:新增一筆記憶並讀回
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"}'正常回應會是包含 results 清單的 JSON 物件。每個項目都包含 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 key 執行 mem0
先處理阻礙,因為你在前 5 分鐘內就會遇到它。伺服器映像檔只內建固定的一組 provider library,而 /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。將該 key 指向 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: key 下加入 ollama_models:,然後拉取一個 chat model 與一個 embedding model:
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 unit 執行,例如 直接在 VPS 上執行 Ollama,不要將容器指向 127.0.0.1:11434。在 mem0 容器內,127.0.0.1 就是 mem0 容器。為 mem0 service 設定 extra_hosts: ["host.docker.internal:host-gateway"],在 systemd drop-in 中設定 Environment="OLLAMA_HOST=0.0.0.0:11434",讓 Ollama 監聽 bridge 可連線的位址,並在 firewall 上封鎖 11434。
在進行任何設定前,先詢問模型的 embedding 維度
這一步會決定 retrieval 是否能正常運作。
mem0 的 pgvector store 會以固定的 vector 寬度建立資料表,即 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']))"這會輸出 collection 必須使用的寬度。請將設定寫入檔案,因為透過 shell quoting 貼上 Postgres password,容易讓拼字錯誤進入 production。
{
"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"第二次呼叫會讀回設定,這可確認寫入已成功。接著重複上方的 smoke test。
該 JSON 中有 4 個不明顯的細節。任何一項設定錯誤,都會導致功能失效。
api_key 是字串 ollama,Ollama 會忽略其值。它不能是空字串,因為未設定 key 時,OpenAI client library 會在任何 request 離開 process 前就拋出錯誤。任何非空字串都可使用。
embedding_model_dims 應設定在 vector store 上,embedder 則刻意不設定 embedding_dims。只有在設定 embedding_dims 時,mem0 才會傳送 OpenAI 的 dimensions parameter;未實作 Matryoshka truncation 的 backend 會直接拒絕該 parameter。請在建立資料表時設定寬度,embedder 不要設定此項。
collection_name 是新的設定。mem0 會以 CREATE TABLE IF NOT EXISTS 建立資料表,因此將不同寬度指向現有 collection 完全不會產生作用:舊的 vector(1536) column 仍會保留,而每次插入都會失敗。變更寬度時,必須使用新的 collection name,或手動刪除舊資料表。
openai_base_url 中的 host 是 Compose service name ollama,不是 localhost。容器會在共用 network 上透過 service name 解析彼此。
完全本機化路徑的代價
請如實評估品質。mem0 公開的 benchmark 分數,是使用 frontier model 進行 extraction 所測得,因此應將其視為上限,而不是 8B model 在 VPS 上的預期結果。小型 model 產生的 facts 會較模糊,有時也會在要求 JSON 時回傳 prose;這會表現為 add call 回傳空的 results list,但不產生錯誤。
速度是另一項代價。僅使用 CPU 進行 extraction 時,每次 add call 都可能耗費數秒,而每則儲存的訊息都必須支付這項成本。如果 latency 很重要,使用配備 GPU 的 VPS 才是實際的解決方式。對 8B model 增加 CPU core,效果遠低於一般預期。
無論選擇哪種方式,都必須遵守一項規則:同一個 collection 絕不能混用不同的 embedding model。兩個剛好具有相同寬度的不同 model,所產生的 vector 並不可互相比較。插入會成功,search 會回傳資料列,但結果是錯的,而且任何地方都不會回報錯誤。
備份:不是只有一個資料庫
最常見的 mem0 備份錯誤,是只匯出單一資料庫。init-db.sh 會在預設的 postgres 資料庫旁建立 mem0_app,而兩者儲存的內容不同。postgres 資料庫儲存 pgvector collections,也就是記憶資料。mem0_app 儲存使用者、工作階段、API keys 與請求日誌。
只還原 postgres 時,記憶資料會恢復,但所有帳戶與 API key 都會遺失,因此沒有任何身分驗證可以讀取這些資料。請在一個指令中同時匯出兩個資料庫及 roles:
docker compose exec -T postgres pg_dumpall -U postgres --clean \
| gzip > "mem0-$(date +%F).sql.gz"history volume 與 Postgres 分開,必須另外備份:
docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
alpine tar czf /backup/mem0-history.tgz -C /data .Docker 會在 volume 名稱前加上 project name。請先使用 docker volume ls 確認名稱,再假設 mem0_mem0_history。
請將備份還原到 scratch container,並在確認 row counts 後再相信備份內容:
gunzip -c mem0-2026-08-03.sql.gz \
| docker compose exec -T postgres psql -U postgres -d postgres從未還原過的備份只是假設。確認 dump 正確後,請使用 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 表示集合建立時使用的維度,與 embedder 回傳的維度不同。在向量儲存中設定 embedding_model_dims,並使用新的 collection_name。
搜尋結果包含沒有意義的資料列 通常發生在更換模型後,且其他地方完全沒有錯誤。維度仍然相符,因此資料庫認為設定正確,但兩個模型會將同一句話放在不同的位置。請建立新的集合,然後重新新增資料。
mem0 日誌在連線至 Ollama 時出現 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 來自該 repo 的 make up target。當 3000 或 8888 已被占用時,該 target 會拒絕啟動。請使用 lsof -iTCP:3000 -sTCP:LISTEN 找出占用連接埠的程序。
FAQ
執行具備圖形記憶體的 mem0 時,仍需要 Neo4j 嗎?
不需要。2026 年 4 月發布的新記憶演算法,已從開放原始碼 SDK 移除 graph_store 與 enable_graph 設定鍵。實體擷取現在會在一般 add 作業期間執行,並將結果寫入名為 <collection_name>_entities 的第二個 pgvector collection,因此不需要外部圖形資料庫、額外容器或遷移步驟。代價是搜尋結果不再包含 relations 欄位。實體現在會提高記憶的排名,而不是提供可供遍歷的邊,因此需要遍歷這些關係的應用程式,必須在 mem0 外部自行使用圖形儲存區。
執行自架 mem0 伺服器所需的最小 VPS 規格為何?
語言模型託管在其他位置時,2 GB RAM 與約 4 GB 可用磁碟空間,就足以執行 API 容器、Postgres 與 dashboard。資源最吃緊的是首次建置,因為編譯 Next.js dashboard 比執行它需要更多記憶體;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",並將 "openai_base_url": "http://ollama:11434/v1" 設為任意非空的 api_key,llm 與 embedder 都要如此設定。Ollama 會忽略該 key,而內建的 provider 檢查會通過,因為實際使用的 provider 確實是 openai。
為何切換至本機 embedding model 後,mem0 沒有回傳結果?
因為 pgvector table 是以固定維度建立的。embedding_model_dims 預設回傳 1536 維,nomic-embed-text 回傳 768 維,而 Postgres 會以 expected 1536 dimensions, not 768 拒絕插入。mem0 使用 CREATE TABLE IF NOT EXISTS 建立 table,因此只變更數值不會影響現有 collection。請將 embedding_model_dims 設為模型的實際維度,呼叫 /v1/embeddings 並計算其回傳值的數量以確認維度,同時為 vector store 指定新的 collection_name。