วิธี self-host mem0 บน VPS พร้อมตั้งค่า Ollama และ RAM
เรียนรู้วิธีติดตั้ง mem0 บน VPS ของคุณเองด้วย Docker Compose พร้อมคำแนะนำการใช้ RAM จริง 8GB สำหรับรัน Ollama และการตั้งค่า TLS เพื่อความปลอดภัยในการเชื่อมต่อ API
ต้นทุนการใช้ RAM จริงสำหรับการ self-host mem0 บน VPS
การ self-host mem0 หมายถึงการรันคอนเทนเนอร์ 3 ตัว ได้แก่ FastAPI memory server, Postgres ที่ติดตั้งส่วนขยาย pgvector และแดชบอร์ด Next.js โดย mem0 ทำหน้าที่เป็นเลเยอร์หน่วยความจำสำหรับเอเจนต์ เมื่อคุณส่งบทสนทนาเข้าไป โมเดลภาษาจะดึงข้อเท็จจริงที่สำคัญออกมาจากบทสนทนานั้น แล้วจัดเก็บข้อเท็จจริงเหล่านั้นในรูปแบบเวกเตอร์ เพื่อให้การสืบค้นในภายหลังสามารถดึงข้อมูลที่เกี่ยวข้องกลับมาได้
ให้เตรียม RAM ไว้ประมาณ 1 GB สำหรับคอนเทนเนอร์ทั้ง 3 ตัว และพื้นที่ดิสก์ 3 ถึง 4 GB หลังจาก build image เรียบร้อยแล้ว VPS ขนาด 2 GB สามารถรันระบบนี้ได้อย่างราบรื่นหากโมเดลภาษาทำงานอยู่ที่อื่น แต่เมื่อรันโมเดลบนเครื่องเดียวกันผ่าน Ollama โมเดลจะใช้ทรัพยากรหลักทั้งหมด โดยโมเดลขนาด 8B ที่ทำ quantization ระดับ 4 bits จะต้องการ RAM ประมาณ 6 GB ดังนั้นการติดตั้งแบบ local ทั้งหมดจึงต้องใช้ RAM เริ่มต้นที่ 8 GB
อย่าเชื่อตัวเลขจากบล็อกโพสต์เพียงอย่างเดียว รวมถึงโพสต์นี้ด้วย ให้ทำการวัดผลจาก stack ที่คุณสร้างขึ้นจริง
docker compose ps
docker stats --no-stream
docker system df -vdocker stats ใช้สำหรับแสดงค่า resident memory ของแต่ละคอนเทนเนอร์ ส่วน docker system df -v ใช้สำหรับแสดงขนาดดิสก์ที่แต่ละ image และ volume ใช้งานอยู่
สถานะการทำงานปกติไม่ใช่ช่วงที่ใช้ทรัพยากรสูงสุด docker compose up -d --build จะทำการ compile แดชบอร์ด Next.js ซึ่งขั้นตอนการ build ด้วย Node นี้เป็นช่วงที่กินทรัพยากรมากที่สุดของการติดตั้งทั้งหมด บน VPS ขนาด 1 GB ตัว kernel out-of-memory killer จะหยุดการทำงานดังกล่าวและทำให้การ build จบลงด้วย exit code 137 ให้ตรวจสอบสาเหตุให้แน่ชัดก่อนที่จะสรุปว่าเป็นบั๊กของ Docker:
dmesg -T | grep -i "killed process"หากการใช้เซิร์ฟเวอร์ดูจะเป็นการจัดการที่เกินความจำเป็นสำหรับสิ่งที่คุณต้องการ ยังมีทางเลือกที่เล็กกว่าให้ใช้งานจริง ทั้ง local agent memory store ที่ไม่ต้องใช้เซิร์ฟเวอร์เลย และ หน่วยความจำที่ทำงานอยู่ภายใน Claude Code เอง ซึ่งทั้งสองวิธีนี้ไม่ต้องพึ่งพาฐานข้อมูล คุณสามารถกลับมาพิจารณาวิธีนี้ได้เมื่อเอเจนต์หลายตัวหรือเครื่องหลายเครื่องจำเป็นต้องอ่านหน่วยความจำชุดเดียวกัน
ฉันจำเป็นต้องใช้ Neo4j สำหรับ graph memory ของ mem0 หรือไม่
ไม่จำเป็น หากคู่มือใดแนะนำให้คุณเพิ่ม container ของ Neo4j แสดงว่าคู่มือนั้นเก่ากว่าตัวซอร์สโค้ดปัจจุบัน
ในอดีต graph memory ใน mem0 หมายถึงการใช้ฐานข้อมูลกราฟภายนอก โดยตั้งค่าภายใต้คีย์ graph_store และกำหนดให้ enable_graph เป็น true อัลกอริทึมหน่วยความจำแบบใหม่ที่ปล่อยออกมาในเดือนเมษายน 2026 ได้นำคีย์ทั้งสองออกจาก open source SDK แล้ว ปัจจุบันการสกัดเอนทิตี (entity extraction) จะทำงานอยู่ภายในขั้นตอนการเพิ่มข้อมูลปกติ และเอนทิตีเหล่านั้นจะถูกเขียนลงใน pgvector collection ชุดที่สองซึ่งตั้งชื่อตาม collection หลักของคุณโดยมี _entities ต่อท้าย ไม่จำเป็นต้องทำการย้ายข้อมูล (migration) ใดๆ ระบบเชื่อมโยงเอนทิตีในตัวจะเริ่มทำงานทันทีในการเรียกใช้คำสั่งเพิ่มข้อมูลครั้งถัดไป
การตัด graph store ออกช่วยประหยัดทรัพยากรของ JVM container ทั้งในส่วนของ heap และขนาด image ที่ลดลงไปหลายร้อยเมกะไบต์ บน VPS ขนาด 2 GB นี่คือความแตกต่างระหว่างการที่ระบบทำงานได้ปกติกับการที่ระบบต้องใช้ swap
นี่คือสิ่งที่คุณจะสูญเสียไปโดยสรุป ผลลัพธ์การค้นหาเคยมีฟิลด์ relations ที่แสดงรายการความสัมพันธ์ (edges) ระหว่างเอนทิตี ซึ่งฟิลด์ดังกล่าวถูกนำออกไปแล้ว ปัจจุบันการจับคู่เอนทิตีจะช่วยเพิ่มลำดับความสำคัญของหน่วยความจำในคะแนนรวมเท่านั้น และไม่มีโครงสร้างให้คุณสืบค้นความสัมพันธ์ได้อีกต่อไป หากแอปพลิเคชันของคุณเคยใช้งานความสัมพันธ์เหล่านั้น mem0 จะไม่เก็บข้อมูลดังกล่าวไว้อีกต่อไป และคุณจำเป็นต้องดูแลฐานข้อมูลกราฟด้วยตนเองภายนอก mem0 โดยป้อนข้อมูลผ่านโค้ดของคุณเอง
ไฟล์ compose ใน repository นี้เป็นไฟล์สำหรับพัฒนา
server/docker-compose.yaml ประกาศ name: mem0-dev และมีความหมายตามนั้น โปรดอ่านก่อนเรียกใช้งาน เนื่องจากมี 5 สิ่งในไฟล์ที่ไม่เหมาะสมสำหรับเซิร์ฟเวอร์
- ไฟล์นี้สร้างอิมเมจจาก
server/dev.Dockerfileและ mount ไดเรกทอรีที่คุณ checkout ไว้ทับลงบนอิมเมจด้วย.:/appดังนั้นคอนเทนเนอร์จะรันสิ่งที่อยู่ในไดเรกทอรีนั้นแทนที่จะเป็นสิ่งที่สร้างไว้ - คำสั่งที่ใช้คือ
rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reloadซึ่งจะติดตั้งmem0aiใหม่จาก PyPI ทุกครั้งที่เริ่มทำงาน ดังนั้นเวอร์ชันที่เซิร์ฟเวอร์ของคุณรันอยู่อาจเปลี่ยนไปในระหว่างการรีสตาร์ทที่คุณไม่ได้ตั้งใจจะอัปเกรด - ขั้นตอน pip ดังกล่าวหมายความว่าหากรีสตาร์ทโดยไม่มีการเชื่อมต่อเครือข่ายภายนอก กระบวนการจะล้มเหลวก่อนที่ uvicorn จะเริ่มทำงาน ส่งผลให้เซิร์ฟเวอร์หน่วยความจำของคุณหยุดทำงานเนื่องจากไม่สามารถเข้าถึง PyPI ได้
--reloadจะเริ่มการทำงานของตัวเฝ้าดูไฟล์ (file watcher) ของ uvicorn ซึ่งมีไว้เพื่อรีสตาร์ทกระบวนการเมื่อคุณแก้ไขโค้ด แต่ในสภาพแวดล้อม production มันจะสิ้นเปลืองหน่วยความจำและสร้างกระบวนการที่สองขึ้นมาโดยไม่มีประโยชน์ ทั้งนี้Dockerfileสำหรับ production ก็มี--reloadอยู่ในCMDเช่นกัน ดังนั้นคุณจึงต้อง override คำสั่งไม่ว่าจะด้วยวิธีใดก็ตาม- พอร์ตที่เปิดใช้งานคือ
"8888:8000","8432:5432"และ"3000:3000"การเปิดพอร์ตโดยไม่ระบุที่อยู่ด้านหน้าจะเป็นการผูกเข้ากับ0.0.0.0ดังนั้น Postgres จะตอบรับการเชื่อมต่อจากอินเทอร์เน็ตสาธารณะบนพอร์ต 8432 ทันทีที่ stack เริ่มทำงาน
ประเด็นสุดท้ายนี้ควรได้รับคำเตือนเป็นพิเศษ Docker จะเปิดพอร์ตโดยการเขียนกฎของตัวเองนำหน้า chain ที่ 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 เพื่อให้ kernel ยอมรับการเชื่อมต่อเหล่านั้นจากภายในเครื่องเท่านั้น ทราฟฟิกทั้งหมดจากภายนอกจะเข้ามาผ่าน reverse proxy ซึ่งเป็นจุดเดียวที่ถือ certificate ไว้
Postgres ไม่มีบล็อก ports เลย คอนเทนเนอร์ mem0 จะเข้าถึงฐานข้อมูลผ่าน mem0_network โดยใช้ชื่อบริการ ดังนั้นการเปิดพอร์ต 8432 จึงไม่มีประโยชน์และทำให้เสียพอร์ตที่เปิดใช้งานโดยไม่จำเป็น ให้ใช้ docker compose exec postgres psql -U postgres ในกรณีที่คุณต้องการเข้าถึง shell
ประวัติการทำงานถูกย้ายจาก bind mount ใน ./history ไปเป็น named volume การใช้ bind mount จะผูกข้อมูลไว้กับ path และ uid หนึ่งๆ บนโฮสต์นี้ ในขณะที่ named volume เป็นออบเจกต์ที่ Docker สามารถทำ snapshot และย้ายไปมาได้ Named volumes เทียบกับ bind mounts อธิบายถึงความเหมาะสมในการใช้งานแต่ละแบบ
คำสั่งดังกล่าวได้ตัด --reload ออกและคง alembic upgrade head ไว้ ให้คงขั้นตอนการ migration นี้ไว้ เพราะหากไม่มีขั้นตอนนี้ แอปจะเริ่มทำงานกับฐานข้อมูลที่ไม่มีตาราง และทุกคำขอจะล้มเหลวในการ query ครั้งแรก
NEXT_PUBLIC_API_URL คือ URL ที่เบราว์เซอร์ของคุณเรียกใช้งาน ดังนั้นจึงต้องเป็นที่อยู่ HTTPS สาธารณะ ไม่ใช่ http://mem0:8000 เนื่องจาก Next.js จะฝังค่า NEXT_PUBLIC_ ลงในโค้ดขณะ build ดังนั้นการเปลี่ยนแปลงค่านี้จึงจำเป็นต้องใช้ docker compose up -d --build mem0-dashboard การ restart แบบปกติจะยังคงใช้ค่าเดิมที่ถูกฝังอยู่ใน 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 ไว้ ชื่อของ flag นี้บอกหน้าที่ของมันอย่างตรงไปตรงมา หากเปิดใช้งาน เซิร์ฟเวอร์จะส่งมอบหน่วยความจำทั้งหมดที่มีให้กับทุกคนที่สามารถเข้าถึงพอร์ตนั้นได้ ให้ตั้งค่า MEM0_TELEMETRY=false หากคุณไม่ต้องการให้เหตุการณ์การเริ่มต้นใช้งาน (onboarding event) ถูกส่งไปยัง upstream
ADMIN_API_KEY จะถูกนำไปเปรียบเทียบกับ header X-API-Key โดยใช้ secrets.compare_digest หากข้อมูลตรงกัน ระบบจะข้ามการค้นหาฐานข้อมูลทั้งหมด นี่คือ credential ระดับ root สำหรับ API ทั้งระบบ ให้ปฏิบัติต่อมันด้วยความระมัดระวัง: ห้ามบันทึกใน shell history, ห้ามนำเข้า git และห้ามคัดลอกไปวางใน prompt การเขียนไฟล์ Compose env และจุดที่ข้อมูลลับรั่วไหลจากไฟล์เหล่านั้น และ การเก็บ API keys ให้พ้นจากบริบทของ agent ล้วนนำมาปรับใช้ได้โดยตรง เนื่องจากผู้เรียกใช้งานเซิร์ฟเวอร์นี้คือเหล่า agent
ค่าที่โหลดจาก env_file จะอยู่ในสภาพแวดล้อมของ container และ docker inspect จะแสดงค่าเหล่านั้นออกมาทั้งหมด ผู้ที่อยู่ในกลุ่ม docker สามารถอ่านค่าเหล่านี้ได้ และทุกคนที่อยู่ในกลุ่ม docker จะมีสิทธิ์เทียบเท่า root บนโฮสต์นั้น
วาง TLS ไว้หน้า API แทนการเปิดพอร์ต 8888
API ตอบสนองที่ 127.0.0.1:8888 และแดชบอร์ดที่ 127.0.0.1:3000 โดยให้ nginx ทำหน้าที่ยุติ TLS (transport layer security) ที่พอร์ต 443 แล้วส่งต่อคำขอไปยังทั้งสองบริการ
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 ในเครื่องมักใช้เวลาประมวลผลนานกว่าค่าเริ่มต้น 60 วินาทีของ nginx ส่งผลให้ผู้เรียกเห็น 504 Gateway Time-out ในขณะที่โมเดลยังคงทำงานอยู่และหน่วยความจำยังคงถูกเขียนลงฐานข้อมูล ผลลัพธ์คือคุณจะได้หน่วยความจำที่ระบบแจ้งว่าบันทึกล้มเหลว
ปิดพอร์ตที่เหลือด้วย นโยบาย default deny ของ ufw โดยเปิดไว้เพียงพอร์ต 22 และ 443 ออกใบรับรองด้วย certbot บน Ubuntu 24.04 หลัง nginx หากเซิร์ฟเวอร์ของคุณทำหน้าที่เป็นหน้าด่านให้กับแอปอื่นอยู่แล้วด้วย Traefik ที่จัดการ routing สำหรับแอป 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"}'การตอบสนองที่ถูกต้องคือออบเจกต์ 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 แทนที่จะไม่มีการกำหนดค่า ดังนั้นผลลัพธ์ที่ไม่ตรงกันมากจะถูกกรองออกไปโดยอัตโนมัติ เมื่อทดสอบผ่าน curl จนใช้งานได้แล้ว ให้ใช้ endpoint เดียวกันนี้ในการเชื่อมต่อกับ agent ไม่ว่าจะเชื่อมต่อโดยตรงหรือผ่าน MCP server ที่รันอยู่บน VPS เดียวกัน
การรัน mem0 โดยไม่ใช้ OpenAI key เลย
เริ่มต้นด้วยปัญหาที่เป็นตัวขัดขวาง เพราะคุณจะพบกับปัญหานี้ภายใน 5 นาทีแรก อิมเมจของเซิร์ฟเวอร์มาพร้อมกับชุดไลบรารีของผู้ให้บริการที่กำหนดไว้ตายตัว และ /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.คุณไม่จำเป็นต้อง build อะไรใหม่ Ollama ให้บริการ API ที่เข้ากันได้กับ OpenAI ที่ /v1 ซึ่งครอบคลุม /v1/chat/completions และ /v1/embeddings และ provider แบบ openai ของ mem0 ก็ยอมรับ 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เพิ่ม ollama_models: ไว้ภายใต้ key ระดับบนสุดคือ volumes: จากนั้นดึงโมเดลแชทและโมเดล embedding มาอย่างละหนึ่งตัว:
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 อยู่แล้ว ตามที่ระบุใน การรัน Ollama โดยตรงบน VPS อย่าชี้ container ไปที่ 127.0.0.1:11434 ภายใน container ของ mem0 นั้น 127.0.0.1 คือตัว container ของ mem0 เอง ให้บริการ mem0 ใช้ extra_hosts: ["host.docker.internal:host-gateway"] และตั้งค่า Environment="OLLAMA_HOST=0.0.0.0:11434" ใน systemd drop-in เพื่อให้ Ollama ฟังบนที่อยู่ที่ bridge network เข้าถึงได้ และปิดพอร์ต 11434 ที่ไฟร์วอลล์ไว้
สอบถามโมเดลเกี่ยวกับ embedding dimension ก่อนที่คุณจะตั้งค่าใดๆ
ขั้นตอนนี้เป็นตัวตัดสินว่าการดึงข้อมูล (retrieval) จะทำงานได้หรือไม่
pgvector store ของ mem0 สร้างตารางด้วยความกว้างของเวกเตอร์ที่กำหนดไว้ตายตัวคือ vector vector(1536) เนื่องจาก embedding_model_dims มีค่าเริ่มต้นเป็น 1536 ซึ่งเป็นความกว้างของ text-embedding-3-small จาก OpenAI ส่วน nomic-embed-text ส่งคืนค่า 768 ค่า ไม่มีส่วนใดใน mem0 ที่เปรียบเทียบตัวเลขสองตัวนี้ ดังนั้นความไม่สอดคล้องกันจะปรากฏจาก Postgres ในการ insert ครั้งแรก:
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 ของคุณต้องใช้ ให้เขียนการตั้งค่าลงในไฟล์ เพราะการวางรหัสผ่าน Postgres ผ่าน shell quoting เป็นสาเหตุที่ทำให้เกิดการพิมพ์ผิดในระบบงานจริง
{
"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 นั้นไม่ชัดเจนนัก และแต่ละจุดจะทำให้เกิดปัญหาหากคุณตั้งค่าผิด
api_key คือสตริง ollama และ Ollama จะเพิกเฉยต่อค่านี้ มันไม่สามารถเป็นค่าว่างได้ เพราะไลบรารีไคลเอนต์ของ OpenAI จะแจ้งเตือนก่อนที่คำขอใดๆ จะออกจากกระบวนการหากไม่มีการตั้งค่า key สตริงใดๆ ที่ไม่ว่างเปล่าสามารถใช้งานได้
embedding_model_dims จะถูกใช้กับ vector store และไม่มีการตั้งค่า embedding_dims บนตัว embedder โดยเจตนา mem0 จะส่งพารามิเตอร์ dimensions ของ OpenAI เฉพาะเมื่อคุณตั้งค่า embedding_dims เท่านั้น และแบ็กเอนด์ที่ไม่รองรับ Matryoshka truncation จะปฏิเสธพารามิเตอร์นั้นทันที ให้ตั้งค่าความกว้างในจุดที่สร้างตาราง และปล่อยตัว embedder ไว้ตามเดิม
collection_name เป็นค่าใหม่ mem0 สร้างตารางด้วย CREATE TABLE IF NOT EXISTS ดังนั้นการชี้ความกว้างที่แตกต่างไปยัง collection ที่มีอยู่เดิมจะไม่มีผลใดๆ คอลัมน์ vector(1536) เดิมจะยังคงอยู่ และการ insert ทุกครั้งจะล้มเหลว การเปลี่ยนความกว้างจำเป็นต้องใช้ชื่อ collection ใหม่ หรือคุณต้องลบตารางเก่าออกด้วยตนเอง
โฮสต์ใน openai_base_url คือชื่อบริการใน Compose คือ ollama ไม่ใช่ localhost container จะระบุตำแหน่งกันและกันด้วยชื่อบริการบนเครือข่ายที่ใช้ร่วมกัน
สิ่งที่คุณต้องแลกกับการใช้งานแบบ local ทั้งหมด
จงซื่อสัตย์กับตัวเองเรื่องคุณภาพ คะแนน benchmark ที่เผยแพร่ของ mem0 ถูกวัดด้วยโมเดลระดับแนวหน้าในการสกัดข้อมูล ดังนั้นให้ถือว่าคะแนนเหล่านั้นเป็นเพดานสูงสุด ไม่ใช่การคาดการณ์สำหรับโมเดลขนาด 8B บน VPS ของคุณ โมเดลขนาดเล็กจะเขียนข้อเท็จจริงที่คลุมเครือกว่า และบางครั้งส่งคืนข้อความเป็นร้อยแก้วในขณะที่ต้องการ JSON ซึ่งจะปรากฏเป็นการเรียก add ที่ส่งคืนรายการ results ว่างเปล่าโดยไม่มีข้อผิดพลาด
ความเร็วคือต้นทุนอีกประการหนึ่ง การสกัดข้อมูลด้วย CPU เพียงอย่างเดียวใช้เวลาหลายวินาทีต่อการเรียก add หนึ่งครั้ง และทุกข้อความที่คุณจัดเก็บจะต้องแลกด้วยเวลานี้ หาก latency มีความสำคัญ การใช้ VPS ที่มี GPU คือทางแก้ที่ตรงไปตรงมาที่สุด การเพิ่มจำนวน CPU core ให้กับโมเดล 8B ช่วยได้น้อยกว่าที่หลายคนคาดคิด
มีกฎข้อหนึ่งที่ต้องยึดถือไม่ว่าคุณจะเลือกอะไรก็ตาม: ห้ามผสมโมเดล embedding ภายใน collection เดียวกัน โมเดลสองตัวที่บังเอิญมีความกว้างเท่ากันจะสร้างเวกเตอร์ที่ไม่สามารถเปรียบเทียบกันได้ การ insert จะสำเร็จ การค้นหาจะส่งคืนแถวข้อมูล และแถวเหล่านั้นจะผิดพลาด โดยไม่มีระบบใดรายงานข้อผิดพลาดออกมาเลย
การสำรองข้อมูล: มีฐานข้อมูลสองชุด ไม่ใช่ชุดเดียว
ข้อผิดพลาดที่พบบ่อยที่สุดในการสำรองข้อมูล mem0 คือการดัมพ์ฐานข้อมูลเพียงชุดเดียว init-db.sh จะสร้าง mem0_app ขึ้นมาควบคู่กับฐานข้อมูลเริ่มต้นอย่าง postgres ซึ่งทั้งสองชุดเก็บข้อมูลที่แตกต่างกัน โดยฐานข้อมูล postgres จะเก็บ pgvector collections ซึ่งก็คือหน่วยความจำ ส่วน mem0_app จะเก็บข้อมูลผู้ใช้, เซสชัน, API keys และบันทึกการร้องขอ (request logs)
หากกู้คืนเฉพาะ postgres หน่วยความจำจะกลับมา แต่บัญชีผู้ใช้และ API keys ทั้งหมดจะหายไป ทำให้ไม่มีข้อมูลใดสามารถยืนยันตัวตนเพื่ออ่านหน่วยความจำเหล่านั้นได้ ดังนั้นให้ดัมพ์ทั้งสองฐานข้อมูลรวมถึง 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 จะใส่ชื่อโปรเจกต์นำหน้าชื่อโวลุ่ม ดังนั้นให้ตรวจสอบชื่อโวลุ่มของคุณด้วย docker volume ls ก่อนที่จะสรุปว่าเป็น mem0_mem0_history
ให้กู้คืนข้อมูลลงในคอนเทนเนอร์เปล่า (scratch container) และตรวจสอบจำนวนแถวข้อมูลก่อนที่จะเชื่อถือข้อมูลที่กู้คืนมา:
gunzip -c mem0-2026-08-03.sql.gz \
| docker compose exec -T postgres psql -U postgres -d postgresการสำรองข้อมูลที่คุณไม่เคยทดสอบกู้คืนถือเป็นเพียงการคาดเดาเท่านั้น เมื่อได้ไฟล์ดัมพ์ที่ถูกต้องแล้ว ให้ส่งข้อมูลออกไปนอกเซิร์ฟเวอร์ด้วย การทำ restic snapshots ไปยังที่จัดเก็บภายนอก เพราะการสำรองข้อมูลที่เก็บไว้บนเซิร์ฟเวอร์เครื่องเดียวกับที่ปกป้องอยู่นั้น ไม่สามารถป้องกันความเสียหายใดๆ ได้เลย
รูปแบบความล้มเหลวและข้อความที่คุณจะพบ
{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} หมายความว่าส่วนหัว (header) หายไปหรือสะกดผิด ชื่อที่ถูกต้องคือ X-API-Key และ curl จะส่งชื่อส่วนหัวตามตัวอักษรที่ระบุ
{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} เมื่อปรากฏในการเพิ่มข้อมูล หมายความว่าคำขอไม่มีข้อมูลดังกล่าว หน่วยความจำ (memory) จำเป็นต้องถูกกำหนดขอบเขตไว้กับบางสิ่ง เนื่องจากตัวกรองการค้นหาจะทำงานกับฟิลด์เหล่านั้นโดยเฉพาะ
LLM provider 'ollama' is not bundled in this image พร้อมกับ HTTP 400 หมายความว่าคุณส่ง "provider": "ollama" ให้ใช้ "provider": "openai" โดยชี้ openai_base_url ไปที่ Ollama
expected 1536 dimensions, not 768 จาก Postgres หมายความว่าคอลเลกชันถูกสร้างขึ้นด้วยความกว้างขนาดหนึ่ง แต่ตัวสร้าง embedding ส่งค่ากลับมาอีกขนาดหนึ่ง ให้ตั้งค่า embedding_model_dims บน vector store และใช้ collection_name ใหม่
การค้นหาคืนค่าแถวที่ไม่สมเหตุสมผล หลังจากเปลี่ยนโมเดล โดยไม่มีข้อผิดพลาดใดๆ ปรากฏขึ้น ความกว้างยังคงตรงกันทำให้ฐานข้อมูลทำงานได้ปกติ แต่โมเดลสองตัววางประโยคเดียวกันไว้ในตำแหน่งที่ต่างกัน ให้เริ่มคอลเลกชันใหม่และเพิ่มข้อมูลเข้าไปใหม่
Connection refused ใน log ของ mem0 ขณะเชื่อมต่อกับ Ollama มักหมายถึง 127.0.0.1 ใน openai_base_url ภายในคอนเทนเนอร์ ที่อยู่ดังกล่าวคือตัวคอนเทนเนอร์เอง ให้ใช้ชื่อบริการ (service name) หรือใช้ host gateway ในกรณีที่ Ollama ทำงานอยู่บนโฮสต์
504 Gateway Time-out จาก nginx ในระหว่างการเพิ่มข้อมูล หมายความว่าโมเดลใช้เวลาประมวลผลนานกว่า proxy_read_timeout ให้เพิ่มค่าดังกล่าว และตรวจสอบว่าหน่วยความจำถูกเขียนลงไปแล้วหรือไม่ก่อนที่จะลองส่งคำขอใหม่อีกครั้ง
exit code 137 ในระหว่าง docker compose up --build คือการที่ out-of-memory killer สั่งหยุดการ build dashboard ให้เพิ่ม swap หรือ build image บนเครื่องที่มีทรัพยากรสูงกว่าแล้ว push ไปยัง registry
error: port 3000 is already in use มาจากเป้าหมาย make up ของ repository ซึ่งปฏิเสธที่จะเริ่มทำงานเมื่อพอร์ต 3000 หรือ 8888 ถูกใช้งานอยู่ ให้ค้นหา process ที่เป็นเจ้าของพอร์ตด้วย lsof -iTCP:3000 -sTCP:LISTEN
FAQ
ฉันยังจำเป็นต้องใช้ Neo4j เพื่อรัน mem0 พร้อมฟีเจอร์ graph memory หรือไม่?
ไม่จำเป็น อัลกอริทึมหน่วยความจำใหม่ที่ปล่อยออกมาในเดือนเมษายน 2026 ได้นำ configuration key graph_store และ enable_graph ออกจาก open source SDK แล้ว ปัจจุบันการดึงข้อมูล entity จะทำงานระหว่างการเพิ่มข้อมูลตามปกติและเขียนลงใน pgvector collection ชุดที่สองที่ชื่อ <collection_name>_entities ดังนั้นจึงไม่มีการใช้ฐานข้อมูลกราฟภายนอก ไม่ต้องใช้ container เพิ่มเติม และไม่มีขั้นตอนการย้ายข้อมูล สิ่งที่เปลี่ยนไปคือฟิลด์ relations ในผลลัพธ์การค้นหาจะไม่มีอยู่อีกต่อไป ปัจจุบัน entity จะทำหน้าที่เพิ่มอันดับ (ranking) ของหน่วยความจำแทนการให้เส้นเชื่อม (edges) สำหรับการไล่ดูข้อมูล ดังนั้นแอปพลิเคชันที่เคยใช้การไล่ดูความสัมพันธ์เหล่านั้นจำเป็นต้องมี graph store ของตนเองแยกต่างหากนอก mem0
VPS ขนาดเล็กที่สุดที่สามารถรัน mem0 server แบบ self-hosted ได้คือเท่าใด?
ในกรณีที่โฮสต์ language model ไว้ที่อื่น RAM ขนาด 2 GB และพื้นที่ดิสก์ว่างประมาณ 4 GB ก็เพียงพอสำหรับ API container, Postgres และแดชบอร์ด ช่วงที่ต้องใช้ทรัพยากรมากที่สุดคือการ build ครั้งแรก เนื่องจากการคอมไพล์ Next.js แดชบอร์ดใช้หน่วยความจำมากกว่าการรันปกติ และเครื่องขนาด 1 GB จะถูกสั่ง kill กระบวนการ build ด้วยข้อผิดพลาด exit code 137 หากรัน Ollama บนเซิร์ฟเวอร์เดียวกัน ให้เผื่อขนาดสำหรับโมเดลด้วย โดยโมเดลขนาด 8B ที่ทำ 4-bit quantisation จะใช้ RAM ประมาณ 6 GB ดังนั้นควรวางแผนใช้ RAM อย่างน้อย 8 GB
ฉันสามารถรัน mem0 โดยไม่ใช้ OpenAI API key ได้หรือไม่?
ได้ โดยผ่าน endpoint ที่รองรับ OpenAI ของ Ollama การตั้งค่า "provider": "ollama" จะล้มเหลวเนื่องจาก server image รวมมาเฉพาะไลบรารี openai, anthropic และ gemini เท่านั้นและจะส่งกลับ HTTP 400 ให้คงค่า "provider": "openai" ไว้และตั้งค่า "openai_base_url": "http://ollama:11434/v1" ด้วย api_key ใดๆ ที่ไม่ว่างเปล่า ทั้งสำหรับ llm และ embedder โดย Ollama จะเพิกเฉยต่อ key ดังกล่าว และการตรวจสอบ provider ที่รวมมาจะผ่านเนื่องจาก provider คือ openai จริงๆ
ทำไม mem0 ถึงไม่ส่งคืนผลลัพธ์หลังจากที่ฉันเปลี่ยนไปใช้ local embedding model?
เนื่องจากตาราง pgvector ถูกสร้างขึ้นด้วยความกว้างที่กำหนดไว้ตายตัว โดยค่าเริ่มต้นของ embedding_model_dims คือ 1536 ในขณะที่ nomic-embed-text ส่งคืนค่า 768 ทำให้ Postgres ปฏิเสธการ insert ข้อมูลด้วยข้อผิดพลาด expected 1536 dimensions, not 768 ทาง mem0 สร้างตารางด้วย CREATE TABLE IF NOT EXISTS ดังนั้นการเปลี่ยนตัวเลขเพียงอย่างเดียวจึงไม่มีผลกับ collection ที่มีอยู่เดิม ให้ตั้งค่า embedding_model_dims ให้ตรงกับความกว้างจริงของโมเดลของคุณ ยืนยันความกว้างนั้นโดยการเรียกใช้ /v1/embeddings และนับจำนวนค่าที่ส่งกลับมา พร้อมกับกำหนด collection_name ใหม่ให้กับ vector store ในเวลาเดียวกัน