SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-10-26

Self-host mem0 trên VPS cần bao nhiêu RAM?

Tự chạy mem0 trên VPS với mức RAM thực tế, Compose bind localhost, TLS trước API và Ollama local. VPS 2 GB đủ nếu model chạy ở máy khác.

Chi phí RAM thực tế khi self-hosting mem0 trên VPS

Self-hosting mem0 nghĩa là chạy 3 container: memory server FastAPI, Postgres với extension pgvector và dashboard Next.js. mem0 là một lớp memory cho agent. Bạn gửi một cuộc hội thoại vào đó, language model trích xuất các thông tin bền vững từ cuộc hội thoại, rồi lưu các thông tin đó dưới dạng vector để truy vấn sau này có thể lấy lại những thông tin liên quan.

Hãy dự trù khoảng 1 GB resident memory cho 3 container và 3 đến 4 GB disk sau khi build xong các image. VPS 2 GB chạy ổn khi language model nằm ở nơi khác. Khi model chạy trên cùng máy qua Ollama, model sẽ dùng phần tài nguyên lớn hơn hẳn mọi thành phần khác: model 8B được quantise xuống 4 bit cần khoảng 6 GB riêng, nên cấu hình chạy hoàn toàn local cần từ 8 GB.

Đừng lấy các con số này từ một bài blog, kể cả bài này. Hãy đo chính stack bạn đã build.

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

docker stats in resident memory theo từng container. docker system df -v in dung lượng disk mà từng image và từng volume đang sử dụng.

Mức sử dụng ổn định không phải mức đỉnh. docker compose up -d --build build dashboard Next.js, và quá trình build bằng Node này là thời điểm dùng nhiều tài nguyên nhất trong toàn bộ quá trình cài đặt. Trên VPS 1 GB, kernel out-of-memory killer sẽ dừng quá trình này và build kết thúc với exit code 137. Hãy xác nhận nguyên nhân trước khi tìm lỗi trong Docker:

dmesg -T | grep -i "killed process"

Nếu một server có vẻ quá phức tạp so với nhu cầu của bạn, vẫn có các lựa chọn nhỏ gọn hơn. memory store local cho agent, không cần server và memory chạy ngay bên trong Claude Code đều không cần database. Hãy quay lại giải pháp này khi nhiều agent hoặc nhiều máy cần đọc chung một memory.

Bạn có cần Neo4j cho graph memory của mem0 không?

Không. Nếu một hướng dẫn yêu cầu bạn thêm container Neo4j, hướng dẫn đó cũ hơn code hiện tại.

Trước đây, graph memory trong mem0 có nghĩa là dùng một cơ sở dữ liệu đồ thị bên ngoài, được cấu hình trong key graph_store với enable_graph đặt thành true. Thuật toán memory mới, được phát hành vào tháng 4 năm 2026, đã xóa cả hai key khỏi SDK mã nguồn mở. Việc trích xuất entity hiện chạy trong luồng add thông thường, và các entity được ghi vào một collection pgvector thứ hai có tên dựa trên collection chính, kèm _entities. Không có bước thiết lập riêng: collection này được tạo trong lần đầu sử dụng, nên tính năng liên kết entity tích hợp sẵn sẽ hoạt động từ lần gọi add tiếp theo.

Bỏ graph store giúp loại một container JVM, phần heap của nó và vài trăm megabyte image. Trên VPS 2 GB, đây có thể là khác biệt giữa việc hệ thống chạy bình thường và phải dùng swap.

Bạn mất các tính năng sau. Trước đây, kết quả tìm kiếm có trường relations liệt kê các cạnh giữa các entity. Trường đó đã bị xóa. Các kết quả khớp entity hiện chỉ làm tăng vị trí của memory trong điểm tổng hợp, và không còn cấu trúc nào để duyệt. Nếu ứng dụng của bạn từng duyệt các quan hệ đó, mem0 không còn lưu chúng nữa. Bạn phải tự vận hành một graph database bên ngoài mem0 và dùng code của mình để cập nhật dữ liệu vào đó.

Tệp compose trong repo là cấu hình compose dành cho môi trường phát triển

server/docker-compose.yaml khai báo name: mem0-dev, và nó thực sự làm đúng như vậy. Hãy đọc tệp này trước khi chạy, vì có 5 điểm không phù hợp với máy chủ.

  • Tệp build từ server/dev.Dockerfile rồi mount thư mục checkout của bạn đè lên image bằng .:/app. Vì vậy, container chạy nội dung hiện có trong thư mục đó thay vì nội dung bạn đã build.
  • Command của tệp là rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --reload. Command này cài lại mem0ai từ PyPI sau mỗi lần khởi động. Vì vậy, phiên bản chạy trên máy chủ có thể thay đổi trong một lần restart mà bạn không nghĩ là nâng cấp.
  • Cùng bước pip đó cũng khiến lần restart thất bại nếu máy chủ không có network outbound, trước khi uvicorn kịp chạy. Khi đó memory server bị dừng vì không truy cập được PyPI.
  • --reload khởi động file watcher của uvicorn. Tính năng này dùng để restart process khi bạn chỉnh sửa code. Trong production, nó chỉ tốn memory và thêm một process mà không có tác dụng. Dockerfile của production cũng chứa --reload trong CMD, nên dù thế nào bạn cũng phải override command.
  • Các port được publish là "8888:8000", "8432:5432" và "3000:3000". Một port được publish mà không có địa chỉ đứng trước sẽ bind vào 0.0.0.0. Vì vậy, Postgres sẽ nhận kết nối từ Internet công cộng trên port 8432 ngay khi stack khởi động.

Điểm cuối cùng cần một cảnh báo riêng. Docker publish port bằng cách thêm rule của chính nó lên trước chain do ufw quản lý. Vì vậy, ufw deny 8432 không đóng được port của container đã publish. Docker publish port bỏ qua ufw giải thích các rule liên quan.

Một file Compose cho server thực

Làm việc bên trong server/, giữ nguyên init-db.sh tại vị trí hiện tại và thay docker-compose.yaml bằng nội dung sau.

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

Có 5 thay đổi quan trọng ở đây. Mỗi thay đổi đều có lý do.

Mỗi mục ports đều bắt đầu bằng 127.0.0.1, nên kernel chỉ chấp nhận các kết nối đó từ chính máy này. Mọi kết nối từ bên ngoài đều đi qua reverse proxy. Đây là thành phần duy nhất giữ certificate.

Postgres hoàn toàn không có block ports. Container mem0 kết nối đến Postgres qua mem0_network bằng tên service, nên publish cổng 8432 không mang lại lợi ích gì mà chỉ mở thêm một cổng. Dùng docker compose exec postgres psql -U postgres khi cần mở shell.

History được chuyển từ bind mount ./history sang named volume. Bind mount gắn dữ liệu với một đường dẫn và một uid cụ thể trên host này, còn named volume là một đối tượng mà Docker có thể snapshot và di chuyển. Named volume và bind mount giải thích khi nào nên dùng từng loại.

Lệnh này bỏ --reload và giữ alembic upgrade head. Hãy giữ bước migration đó. Nếu bỏ bước này, app sẽ khởi động với database chưa có bảng và mọi request sẽ fail ngay ở query đầu tiên.

NEXT_PUBLIC_API_URL là URL mà trình duyệt gọi, nên phải là địa chỉ HTTPS public, không phải http://mem0:8000. Next.js inline các giá trị NEXT_PUBLIC_ tại thời điểm build, nhưng image của dashboard này ghi sẵn một placeholder thay vào đó, rồi entrypoint thay placeholder bằng giá trị thực của container khi container khởi động. Vì vậy, thay đổi giá trị này cần docker compose up -d mem0-dashboard để tạo lại container, không cần build lại. docker compose restart mem0-dashboard tái sử dụng container cũ, trong đó các file vẫn chứa giá trị cũ, nên dashboard tiếp tục gọi sai host.

Secrets nằm trong .env, và .env không được đưa lên Internet

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

Thiết lập POSTGRES_PASSWORD, JWT_SECRET và ADMIN_API_KEY. Giữ nguyên AUTH_DISABLED=false. Tên của flag này mô tả đúng chức năng: khi bật, server cung cấp toàn bộ nội dung memory mà nó đang giữ cho bất kỳ ai truy cập được vào port. Thiết lập MEM0_TELEMETRY=false nếu bạn không muốn gửi sự kiện onboarding lên upstream.

ADMIN_API_KEY được so sánh với header X-API-Key bằng secrets.compare_digest. Khi khớp, server bỏ qua hoàn toàn bước tra cứu theo từng key trong database. Request khi đó chạy dưới danh tính user đầu tiên từng được tạo trên server. Đây là root credential cho toàn bộ API. Hãy bảo vệ nó như root credential: không để trong shell history, không commit vào git và không dán vào prompt. File env của Compose và nơi secret bị rò rỉ từ đó và giữ API key ngoài context của agent đều áp dụng trực tiếp, vì agent là các caller của server này.

Các giá trị được nạp từ env_file nằm trong environment của container, và docker inspect in toàn bộ các giá trị đó. Bất kỳ ai thuộc group docker đều có thể đọc chúng, còn bất kỳ ai thuộc group docker thực tế đều có quyền root trên host.

Đặt TLS trước API thay vì mở cổng 8888

API lắng nghe trên 127.0.0.1:8888 và dashboard lắng nghe trên 127.0.0.1:3000. nginx thực hiện TLS termination (bảo mật tầng truyền tải) tại cổng 443 rồi chuyển tiếp đến cả hai.

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|reset|generate-instructions|auth|api-keys|entities|requests|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;
    }
}

Mã JavaScript chạy trong trình duyệt của dashboard gọi mọi API path theo mẫu đó tại NEXT_PUBLIC_API_URL, bao gồm /entities và /requests. Bỏ sót một path, nginx sẽ chuyển request đó đến Next.js. Khi đó trang dashboard cần path này sẽ nhận 404 thay vì dữ liệu.

proxy_read_timeout quan trọng hơn vẻ ngoài. Một request add sẽ bị block trong lúc language model đọc cuộc hội thoại và trích xuất thông tin. Model 8B chạy cục bộ trên CPU thường mất nhiều thời gian hơn thời gian chờ mặc định 60 giây của nginx. Khi đó caller nhận 504 Gateway Time-out, dù model vẫn đang xử lý và memory vẫn được ghi. Kết quả là bạn có một memory mà hệ thống báo là đã thất bại.

Đóng các cổng còn lại bằng chính sách ufw mặc định từ chối, chỉ để mở 22, 80 và 443. Cấp certificate bằng certbot trên Ubuntu 24.04 phía sau nginx. Cổng 80 phải tiếp tục truy cập được vì quy trình này dùng HTTP-01 challenge. Let's Encrypt chỉ chạy challenge này trên cổng 80, cả khi cấp certificate lẫn mỗi lần gia hạn. Nếu đóng cổng 80, bạn phải chuyển sang DNS-01 challenge. Nếu máy đã dùng Traefik định tuyến nhiều ứng dụng Compose ở phía trước, hãy thêm mem0 vào router đó thay vì cài thêm một reverse proxy.

Kiểm thử nhanh: thêm một memory rồi đọc lại

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"}'

Phản hồi hợp lệ là một JSON object có danh sách results. Mỗi entry chứa id, phần văn bản memory đã trích xuất và "event": "ADD". Algorithm hiện tại chỉ trả về các event ADD. Các event UPDATE và DELETE đã bị loại bỏ, nên việc chúng không xuất hiện không phải là lỗi.

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}'

Fact về Postgres 17 phải được trả về kèm score. Truyền identifier bên trong filters như minh họa. user_id ở cấp cao nhất vẫn hoạt động, và server ghi log Top-level user_id in /search is deprecated. Use filters={...} instead. mỗi lần bạn sử dụng nó.

Dọn dẹp dữ liệu sau khi kiểm thử để dữ liệu test không làm nhiễu các lần search thực tế:

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

Nếu search trả về ít row hơn dự kiến, hãy kiểm tra các giá trị mặc định trước khi kết luận retrieval có vấn đề. Trong release hiện tại, top_k mặc định là 20, giảm từ 100, còn threshold mặc định là 0.1 thay vì không giới hạn. Vì vậy, các match yếu sẽ được tự động lọc. Sau khi curl hoạt động, bạn có thể kết nối chính các endpoint này vào một agent, trực tiếp hoặc thông qua một MCP server chạy trên cùng VPS.

Chạy mem0 hoàn toàn không cần OpenAI key

Hãy bắt đầu với điểm chặn này, vì bạn sẽ gặp nó trong 5 phút đầu tiên. Image của server chỉ có sẵn một tập thư viện provider cố định, và /configure từ chối mọi thứ nằm ngoài tập đó:

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.

Bạn không cần rebuild gì cả. Ollama cung cấp API tương thích với OpenAI tại /v1, hỗ trợ /v1/chat/completions và /v1/embeddings, còn openai provider của mem0 chấp nhận openai_base_url. Trỏ key đó đến Ollama thì bước kiểm tra đi kèm sẽ pass, vì provider thực sự là openai. Chỉ có địa chỉ thay đổi.

Thêm Ollama vào cùng Compose project:

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

Thêm ollama_models: bên dưới key cấp cao nhất volumes:, rồi pull một chat model và một 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

Nếu Ollama đã chạy trên host dưới dạng systemd unit, như trong chạy Ollama trực tiếp trên VPS, đừng trỏ container đến 127.0.0.1:11434. Bên trong mem0 container, 127.0.0.1 chính là mem0 container. Gán extra_hosts: ["host.docker.internal:host-gateway"] cho mem0 service, đặt Environment="OLLAMA_HOST=0.0.0.0:11434" trong systemd drop-in để Ollama lắng nghe trên một địa chỉ mà bridge có thể truy cập, và giữ cổng 11434 bị chặn trên firewall.

Hỏi model về embedding dimension trước khi cấu hình bất kỳ thứ gì

Bước này quyết định retrieval có hoạt động hay không.

pgvector store của mem0 tạo table với vector width cố định là vector vector(1536), vì embedding_model_dims mặc định là 1536, bằng width của text-embedding-3-small của OpenAI. nomic-embed-text trả về 768 giá trị. mem0 không so sánh hai con số này, nên mismatch chỉ lộ ra từ Postgres ở lần insert đầu tiên:

expected 1536 dimensions, not 768

Cũng đừng tin con số trong đoạn này. Hãy hỏi model:

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']))"

Lệnh đó in ra width mà collection của bạn phải sử dụng. Ghi cấu hình vào file, vì paste Postgres password qua shell quoting là cách typo lọt vào 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"

Lệnh gọi thứ hai đọc lại cấu hình. Đây là bước kiểm tra việc ghi đã thành công. Sau đó chạy lại smoke test ở trên.

Có 4 chi tiết trong JSON đó không dễ nhận thấy. Mỗi chi tiết đều có thể làm hỏng hệ thống nếu cấu hình sai.

api_key là chuỗi ollama, và Ollama bỏ qua giá trị của nó. Giá trị này không được để trống, vì OpenAI client library sẽ raise trước khi request rời khỏi process nếu không có key. Bất kỳ chuỗi không rỗng nào cũng được.

embedding_model_dims nằm trên vector store, còn embedder cố ý không có embedding_dims. mem0 chỉ gửi parameter dimensions của OpenAI khi bạn đặt embedding_dims, và các backend không hỗ trợ Matryoshka truncation sẽ từ chối parameter này ngay lập tức. Đặt width tại nơi tạo table và để embedder giữ nguyên.

collection_name là mới. mem0 tạo table bằng CREATE TABLE IF NOT EXISTS, nên trỏ một width khác vào collection hiện có sẽ không làm gì cả: column vector(1536) cũ vẫn giữ nguyên và mọi insert đều fail. Thay đổi width cần một collection name mới, hoặc bạn phải tự drop table cũ.

Host trong openai_base_url là Compose service name ollama, không phải localhost. Các container phân giải lẫn nhau bằng service name trên shared network.

Chi phí của một quy trình hoàn toàn local

Hãy đánh giá chất lượng một cách thực tế. Các benchmark score nổi bật của mem0 đến từ managed platform của họ. README cho biết platform này có các tối ưu độc quyền không có trong open source SDK và chạy trên model stack không được nêu tên. Research paper đứng sau kết quả LOCOMO ban đầu chạy mọi model call trên GPT-4o-mini, một hosted model. Dù theo cách nào, hãy xem các score đó là mức trần, không phải dự báo cho model 8B trên VPS của bạn. Model nhỏ thường ghi các fact mơ hồ hơn và đôi khi trả về prose dù được yêu cầu JSON. Khi đó, add call có thể trả về một list results rỗng mà không báo lỗi.

Tốc độ là chi phí thứ hai. Extraction chỉ dùng CPU mất vài giây cho mỗi add call, và mỗi message bạn lưu đều phải chịu chi phí đó. Model càng lan man ngoài JSON được yêu cầu thì vấn đề càng nghiêm trọng. Vì vậy, giới hạn reply bằng num_predict sẽ đặt giới hạn cho thời gian chạy của mỗi add call. Nếu latency này quan trọng, dùng VPS gắn GPU là cách khắc phục thực tế. Tăng thêm CPU core cho model 8B giúp ít hơn nhiều so với kỳ vọng thông thường. Đổi model rẻ hơn đổi máy, và Nemotron 3.5 Lightning trên VPS cung cấp tag cần pull, lượng RAM cần thiết và thông tin CPU-only có đủ nhanh để chấp nhận hay không.

Dù bạn chọn phương án nào, vẫn có một quy tắc không thay đổi: không bao giờ trộn embedding model trong cùng một collection. Hai model khác nhau dù tình cờ có cùng width vẫn tạo ra các vector không thể so sánh. Insert vẫn thành công, search vẫn trả về các row, nhưng các row đó sai mà không có thành phần nào báo lỗi.

Backup: có hai database, không phải một

Lỗi phổ biến nhất khi backup mem0 là chỉ dump một database. init-db.sh tạo mem0_app cùng với database mặc định postgres, và mỗi database lưu một loại dữ liệu khác nhau. Database postgres lưu các collection pgvector, tức các memory. mem0_app lưu user, session, API key và request log. Mỗi ứng dụng self-hosted phân tách state theo cách riêng. Vì vậy, hai photo server làm cùng một việc vẫn cần các lệnh backup khác nhau. Hãy kiểm tra ứng dụng lưu những gì trước khi tin tưởng một bản dump. Ở đầu kia của phạm vi này là trường hợp như một thư viện Jellyfin được dựng lại thành cửa hàng video kiểu thập niên 90. Thư viện đó đọc toàn bộ catalogue từ một service khác nên gần như chỉ cần sao chép configuration của chính nó. Ngược lại, mem0 cần cả hai database. Nếu không, bản restore sẽ không dùng được.

Nếu chỉ restore postgres, các memory sẽ quay lại nhưng toàn bộ account và API key sẽ mất. Khi đó không có gì có thể authenticate để đọc chúng. Hãy dump cả hai database cùng với role bằng một lệnh:

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

Volume history tách biệt với Postgres và cần được sao lưu riêng:

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

Docker thêm tên project vào trước tên volume. Hãy xác nhận tên volume bằng docker volume ls trước khi giả định mem0_mem0_history.

Hãy restore vào một container tạm rồi kiểm tra số lượng row trước khi tin rằng backup hợp lệ:

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

Một backup chưa từng được restore chỉ là phỏng đoán. Khi các bản dump đã đúng, hãy đẩy chúng ra khỏi máy bằng restic snapshot đến storage bên ngoài, vì backup nằm trên chính server mà nó cần bảo vệ thì không bảo vệ được gì.

Các lỗi thường gặp và chính xác chuỗi bạn sẽ thấy

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} có nghĩa là header bị thiếu hoặc viết sai. Tên header là X-API-Key, và curl gửi nguyên văn tên header.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} khi add có nghĩa là request không chứa trường nào trong số đó. Memory phải được gắn với một phạm vi cụ thể, vì bộ lọc tìm kiếm dựa chính xác vào các trường này.

LLM provider 'ollama' is not bundled in this image kèm HTTP 400 có nghĩa là bạn đã gửi "provider": "ollama". Dùng "provider": "openai" với openai_base_url trỏ đến Ollama.

expected 1536 dimensions, not 768 từ Postgres có nghĩa là collection được tạo với một số chiều, còn embedder trả về số chiều khác. Đặt embedding_model_dims trên vector store và dùng một collection_name mới.

Search returns rows that make no sense sau khi đổi model, dù không có lỗi nào ở bất kỳ đâu. Số chiều vẫn khớp nên database không báo lỗi, nhưng hai model đặt cùng một câu ở các vị trí khác nhau. Tạo collection mới rồi add lại dữ liệu.

Connection refused trong log của mem0 khi kết nối đến Ollama thường có nghĩa là 127.0.0.1 trong openai_base_url. Bên trong container, địa chỉ đó trỏ đến chính container. Dùng service name hoặc host gateway khi Ollama chạy trên host.

504 Gateway Time-out từ nginx khi add có nghĩa là model chạy lâu hơn proxy_read_timeout. Tăng giá trị này và kiểm tra xem memory đã được ghi hay chưa trước khi retry request.

exit code 137 trong lúc docker compose up --build có nghĩa là một process đã bị kill bằng SIGKILL, vì signal fatal N được báo cáo là 128 + N và SIGKILL là signal 9. Khi dashboard đang build trên một VPS nhỏ, nguyên nhân có khả năng nhất là out-of-memory killer. Vì vậy, hãy chạy kiểm tra dmesg từ section đầu tiên trước khi xử lý. Nếu kiểm tra cho thấy có process bị kill, hãy thêm swap hoặc build image trên máy lớn hơn rồi push image lên registry.

error: port 3000 is already in use xuất phát từ target make up của repo. Target này từ chối khởi động khi cổng 3000 hoặc 8888 đang được sử dụng. Tìm process đang giữ cổng bằng lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Tôi có còn cần Neo4j để chạy mem0 với graph memory không?

Không. Thuật toán memory mới, được phát hành vào tháng 4 năm 2026, đã xóa các configuration key graph_store và enable_graph khỏi SDK mã nguồn mở. Việc trích xuất entity hiện chạy trong một lần add thông thường và ghi vào collection pgvector thứ hai có tên <collection_name>_entities. Vì vậy, bạn không cần graph database bên ngoài, container bổ sung hay bước setup riêng. Đổi lại, field relations không còn xuất hiện trong kết quả search. Entity hiện làm tăng thứ hạng của memory thay vì cung cấp các edge để duyệt. Vì vậy, ứng dụng từng duyệt các relationship đó phải tự dùng graph store bên ngoài mem0.

VPS nhỏ nhất có thể chạy server mem0 tự host là loại nào?

Nếu language model được host ở nơi khác, 2 GB RAM và khoảng 4 GB disk trống là đủ cho API container, Postgres và dashboard. Giai đoạn dễ thiếu tài nguyên nhất là lần build đầu tiên, vì việc compile dashboard Next.js dùng nhiều memory hơn lúc chạy. Trên máy 1 GB, quá trình build sẽ bị kill với exit code 137. Nếu Ollama chạy trên cùng server, hãy chọn cấu hình theo model. Model 8B với quantisation 4-bit tự nó cần khoảng 6 GB, vì vậy nên dùng 8 GB.

Tôi có thể chạy mem0 mà không cần OpenAI API key không?

Có, thông qua endpoint tương thích với OpenAI của Ollama. Thiết lập "provider": "ollama" sẽ thất bại vì server image chỉ đóng gói các library openai, anthropic và gemini, rồi trả về HTTP 400. Thay vào đó, giữ "provider": "openai" và đặt "openai_base_url": "http://ollama:11434/v1" bằng bất kỳ api_key không rỗng nào cho cả llm và embedder. Ollama bỏ qua key này. Kiểm tra provider đi kèm vẫn thành công vì provider thực sự là openai.

Vì sao mem0 không trả về kết quả sau khi tôi chuyển sang embedding model local?

Vì bảng pgvector được tạo với chiều cố định. embedding_model_dims mặc định là 1536, còn nomic-embed-text trả về 768, nên Postgres từ chối insert với expected 1536 dimensions, not 768. mem0 tạo bảng bằng CREATE TABLE IF NOT EXISTS, vì vậy chỉ thay đổi con số không làm thay đổi collection hiện có. Đặt embedding_model_dims bằng chiều thực của model, xác nhận chiều đó bằng cách gọi /v1/embeddings và đếm số value mà lệnh trả về, đồng thời cấp cho vector store một collection_name mới.

Bài viết này có hữu ích không?