אירוח עצמי של mem0 על גבי VPS: מדריך מעשי
למדו כיצד להריץ mem0 על שרת VPS עם הגדרות Docker Compose מאובטחות. גלו את צריכת ה-RAM האמיתית, איך להגדיר TLS מול ה-API ואיך לחבר את Ollama בצורה מקומית ויציבה.
מהו העלות האמיתית ב-RAM של אירוח עצמי ל-mem0 על גבי VPS
אירוח עצמי של mem0 משמעו הרצת שלוש מכולות: שרת ה-memory מבוסס FastAPI, מסד נתונים Postgres עם תוסף pgvector, ולוח בקרה מבוסס Next.js. mem0 הוא שכבת זיכרון עבור סוכנים (agents). אתם שולחים אליו שיחה, מודל שפה מחלץ ממנה עובדות עמידות, ואלו נשמרות כווקטורים כך ששאילתה מאוחרת יותר תוכל לשלוף את העובדות הרלוונטיות.
הקצו בערך 1 GB של זיכרון resident עבור שלוש המכולות, ו-3 עד 4 GB של שטח דיסק לאחר בניית האימג'ים. שרת VPS עם 2 GB מריץ זאת בנוחות כאשר מודל השפה נמצא במקום אחר. כאשר המודל רץ על אותו שרת באמצעות Ollama, הוא משתלט על כל המשאבים: מודל 8B שעבר קוונטיזציה ל-4 ביטים דורש כ-6 GB בעצמו, ולכן בנייה מקומית מלאה מתחילה ב-8 GB.
אל תסתמכו על מספרים מפוסטים בבלוגים, כולל זה. מדדו את ה-stack שבניתם בפועל.
docker compose ps
docker stats --no-stream
docker system df -vdocker stats מציג את זיכרון ה-resident לכל מכולה. docker system df -v מציג את נפח הדיסק שתופס כל אימג' וכל volume.
מצב עבודה יציב אינו מצב השיא. docker compose up -d --build מבצע קומפילציה ללוח הבקרה של Next.js, ותהליך ה-build של Node הוא הרגע התובעני ביותר בכל ההתקנה. בשרת VPS עם 1 GB, מנגנון ה-out-of-memory killer של ה-kernel עוצר את התהליך והבנייה מסתיימת עם exit code 137. ודאו את סיבת הכשל לפני שאתם מחפשים באג ב-Docker:
dmesg -T | grep -i "killed process"אם שרת מרגיש כמו מנגנון מורכב מדי עבור הצרכים שלכם, קיימות אפשרויות קטנות יותר. אחסון זיכרון סוכן מקומי ללא שרת כלל ו-זיכרון שחי בתוך Claude Code עצמו שניהם מוותרים על מסד הנתונים. חזרו לכאן כאשר מספר סוכנים, או מספר מכונות, צריכים לקרוא את אותם זיכרונות.
האם אני זקוק ל-Neo4j עבור זיכרון גרף ב-mem0?
לא. אם מדריך כלשהו מנחה אתכם להוסיף קונטיינר של Neo4j, המדריך הזה ישן יותר מהקוד הנוכחי.
בעבר, זיכרון גרף ב-mem0 דרש מסד נתונים חיצוני לגרפים, שהוגדר תחת מפתח graph_store עם enable_graph מוגדר כ-true. אלגוריתם הזיכרון החדש, ששוחרר באפריל 2026, הסיר את שני המפתחות הללו מה-SDK בקוד פתוח. חילוץ ישויות מתבצע כעת כחלק מנתיב ה-add הרגיל, והישויות נכתבות לאוסף pgvector שני ששמו נגזר מהאוסף הראשי בתוספת הסיומת _entities. אין צורך לבצע הגירה (migration). קישור ישויות מובנה מתחיל לעבוד באופן אוטומטי בקריאת ה-add הבאה.
הוויתור על מאגר הגרפים חוסך קונטיינר JVM, את ה-heap שלו, ומאות מגה-בייטים של נפח אימג'. בשרת VPS עם 2 GB RAM, זהו ההבדל בין הרצה תקינה לבין כניסה ל-swapping.
להלן מה שאתם מאבדים, באופן ישיר: תוצאות חיפוש נהגו לכלול שדה relations שפירט קשתות בין ישויות. שדה זה אינו קיים עוד. התאמות ישויות מעלות כעת את המיקום של זיכרון בציון המשוקלל, ואין מבנה שניתן לעבור עליו (traverse). אם היישום שלכם נהג לסרוק את הקשרים הללו, mem0 כבר אינו מחזיק אותם, ועליכם לנהל מסד נתונים של גרפים באופן עצמאי מחוץ ל-mem0, שיוזן על ידי הקוד שלכם.
קובץ ה-compose במאגר מיועד לסביבת פיתוח
server/docker-compose.yaml מצהיר על name: mem0-dev, וזו כוונתו. קראו אותו לפני ההרצה, כיוון שחמישה דברים בו אינם מתאימים לשרת.
- הוא מבצע build מתוך
server/dev.Dockerfileומבצע mount לתיקיית העבודה שלכם על גבי ה-image באמצעות.:/app, כך שה-container מריץ את מה שנמצא בתיקייה זו ולא את מה שבניתם. - הפקודה שלו היא
rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. היא מתקינה מחדש אתmem0aiמ-PyPI בכל הפעלה, כך שהגרסה שהשרת מריץ עלולה להשתנות במהלך restart שלא התכוונתם שיהיה שדרוג. - אותו שלב של pip אומר ש-restart ללא גישה לרשת חיצונית ייכשל לפני ש-uvicorn יספיק לרוץ. שרת הזיכרון שלכם יהיה מושבת כי לא ניתן היה להגיע ל-PyPI.
--reloadמפעיל את מנגנון המעקב אחר קבצים של uvicorn. הוא קיים כדי להפעיל מחדש את התהליך כאשר אתם עורכים קוד, והוא צורך זיכרון ומפעיל תהליך נוסף שאין בו צורך בסביבת production. ה-Dockerfileשל ה-production כולל גם הוא--reloadבתוך ה-CMDשלו, כך שאתם דורסים את הפקודה בכל מקרה.- הפורטים המפורסמים הם
"8888:8000","8432:5432"ו-"3000:3000". פורט שמפורסם ללא כתובת מוגדרת לפניו מאזין ל-0.0.0.0, כך ש-Postgres משיב לאינטרנט הציבורי בפורט 8432 ברגע שה-stack עולה.
הנקודה האחרונה הזו ראויה לאזהרה משל עצמה. Docker מפרסם פורט על ידי כתיבת חוקים משלו לפני ה-chain ש-ufw מנהל, לכן ufw deny 8432 לא יחסום פורט של container שפורסם. פרסום פורטים ב-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יש כאן חמישה שינויים חשובים, ולכל אחד מהם יש סיבה.
כל ערך ports מתחיל ב-127.0.0.1, כך שה-kernel מקבל את החיבורים האלה רק מהשרת עצמו. כל תעבורה מבחוץ מגיעה דרך ה-reverse proxy, שהוא הרכיב היחיד שמחזיק בתעודה.
ל-Postgres אין בלוק ports כלל. המכולה mem0 מגיעה אליו דרך mem0_network לפי שם השירות, לכן פרסום הפורט 8432 אינו מועיל ועולה בפורט פתוח מיותר. השתמשו ב-docker compose exec postgres psql -U postgres כאשר אתם זקוקים ל-shell.
ההיסטוריה עוברת מ-bind mount ב-./history ל-named volume. בעוד ש-bind mount קושר את הנתונים לנתיב ול-uid ספציפיים במארח, named volume הוא אובייקט ש-Docker יכול לבצע לו snapshot ולהעביר. המאמר Named volumes against bind mounts מסביר מתי נכון להשתמש בכל אחד מהם.
הפקודה מסירה את --reload ושומרת על alembic upgrade head. הקפידו על שלב ה-migration הזה. בלעדיו, היישום עולה מול מסד נתונים ללא טבלאות, וכל בקשה תיכשל בשאילתה הראשונה.
NEXT_PUBLIC_API_URL הוא ה-URL שהדפדפן שלכם קורא לו, לכן הוא חייב להיות כתובת ה-HTTPS הציבורית ולא http://mem0:8000. הלאה. Next.js מטמיע כל ערך NEXT_PUBLIC_ בזמן ה-build, לכן שינוי שלו מחייב 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 כפי שהוא. השם של הדגל מעיד על תפקידו: כאשר הוא מופעל, השרת חושף את כל הזיכרון שברשותו לכל מי שיכול להגיע לפורט. הגדירו את MEM0_TELEMETRY=false אם אינכם מעוניינים שאירוע ה-onboarding יישלח במעלה הזרם (upstream).
הערך ADMIN_API_KEY מושווה מול ה-header מסוג X-API-Key באמצעות secrets.compare_digest, והתאמה מדלגת על כל חיפוש במסד הנתונים. זהו פרט אימות ברמת root עבור כל ה-API. התייחסו אליו בהתאם: ללא שמירה ב-shell history, ללא העלאה ל-git, וללא הדבקה שלו בתוך prompt. המדריכים יצירת קובצי env ומקומות שמהם סודות דולפים ו-שמירה על מפתחות API מחוץ להקשר של סוכן רלוונטיים שניהם באופן ישיר, כיוון שהקוראים לשרת זה הם סוכנים.
ערכים הנטענים מתוך env_file נמצאים בסביבת המכולה, ו-docker inspect מדפיס אותם במלואם. כל מי שנמצא בקבוצה docker יכול לקרוא אותם, וכל מי שנמצא בקבוצה docker הוא למעשה root על המארח.
הצבת TLS לפני ה-API במקום פתיחת פורט 8888
ה-API משיב בכתובת 127.0.0.1:8888 ולוח הבקרה ב-127.0.0.1:3000. Nginx מבצע TLS termination בפורט 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 לוקח לעיתים קרובות זמן רב יותר מברירת המחדל של Nginx, העומדת על 60 שניות. כתוצאה מכך, הקורא מקבל 504 Gateway Time-out בזמן שהמודל עדיין מעבד את המידע והזיכרון ממשיך להיכתב. התוצאה היא זיכרון שדווח עליו ככזה שנכשל, בעודו נוצר בפועל.
חסמו את שאר הפורטים באמצעות מדיניות default deny ב-ufw, והשאירו רק את 22 ו-443 פתוחים. הנפיקו את התעודה באמצעות certbot על Ubuntu 24.04 מאחורי nginx. אם השרת כבר מנהל יישומים אחרים באמצעות Traefik לניתוב כמה יישומי Compose, הוסיפו את mem0 לנתב הקיים במקום להתקין 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 אמורה לחזור עם ציון (score). העבירו את המזהה בתוך 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, אלו הם אותם ה-endpoints שתחברו לסוכן (agent), בין אם ישירות ובין אם דרך שרת MCP שרץ על אותו VPS.
הרצת mem0 ללא מפתח OpenAI כלל
התחילו מהחסימה, כיוון שתיתקלו בה בחמש הדקות הראשונות. אימג' השרת מגיע עם קבוצה קבועה של ספריות ספקים, ו-/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 מציע API תואם OpenAI ב-/v1, המכסה את /v1/chat/completions ואת /v1/embeddings, והספק openai של mem0 מקבל openai_base_url. כוונו את המפתח הזה אל Ollama והבדיקה המובנית תעבור, כיוון שהספק הוא אכן 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: תחת מפתח ה-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, כפי שמתואר ב-הרצת Ollama ישירות על VPS, אל תכוונו את המכולה ל-127.0.0.1:11434. בתוך מכולת mem0, ה-127.0.0.1 הוא מכולת mem0 עצמה. תנו לשירות mem0 את extra_hosts: ["host.docker.internal:host-gateway"], הגדירו Environment="OLLAMA_HOST=0.0.0.0:11434" בתוך קובץ drop-in של systemd כדי ש-Ollama יאזין לכתובת שה-bridge יכול להגיע אליה, והשאירו את פורט 11434 חסום ב-firewall.
שאלו את המודל מהו ממד ההטמעה שלו לפני הגדרת דבר מה
צעד זה קובע האם השליפה תעבוד בכלל.
מאגר ה-pgvector של mem0 יוצר את הטבלה שלו עם רוחב וקטור קבוע, vector vector(1536), כיוון ש-embedding_model_dims מוגדר כברירת מחדל ל-1536, הרוחב של text-embedding-3-small מבית OpenAI. ה-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']))"זה ידפיס את הרוחב שבו האוסף שלכם חייב להשתמש. כתבו את התצורה לקובץ, כיוון שהדבקת סיסמת Postgres דרך ציטוט ב-shell היא הדרך שבה טעויות הקלדה מגיעות לסביבת הייצור.
{
"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 מעלה שגיאה לפני שכל בקשה עוזבת את התהליך כאשר לא מוגדר מפתח. כל מחרוזת שאינה ריקה תעבוד.
embedding_model_dims מוגדר על מאגר הווקטורים, ובכוונה אין embedding_dims על ה-embedder. mem0 שולח את הפרמטר dimensions של OpenAI רק כאשר מגדירים embedding_dims, ו-backends שאינם מממשים קיטום (truncation) מסוג Matryoshka דוחים את הפרמטר הזה על הסף. הגדירו את הרוחב במקום שבו הטבלה נוצרת, והניחו ל-embedder לנפשו.
collection_name הוא חדש. mem0 יוצר את הטבלה שלו עם CREATE TABLE IF NOT EXISTS, לכן כיוון רוחב שונה לאוסף קיים לא עושה דבר: עמודת ה-vector(1536) הישנה נשארת, וכל הכנסה נכשלת. שינוי רוחב דורש שם אוסף חדש, או מחיקה ידנית של הטבלה הישנה.
המארח ב-openai_base_url הוא שם שירות ה-Compose, ה-ollama, ולא localhost. מכולות מזהות זו את זו לפי שם השירות ברשת המשותפת שלהן.
מה עולה לכם המסלול המקומי המלא
היו כנים עם עצמכם לגבי האיכות. ציוני ה-benchmark שפורסמו עבור mem0 נמדדו עם מודלים חזיתיים (frontier models) שביצעו את החילוץ, לכן התייחסו אליהם כאל תקרה ולא כאל תחזית עבור מודל 8B על ה-VPS שלכם. מודל קטן כותב עובדות מעורפלות יותר, ולעיתים מחזיר פרוזה במקום שבו התבקש JSON, מה שמופיע כקריאת add שמחזירה רשימת results ריקה ללא שגיאה.
מהירות היא העלות הנוספת. חילוץ מבוסס CPU בלבד לוקח שניות לכל קריאת add, וכל הודעה שאתם מאחסנים משלמת את המחיר הזה. אם השיהוי הזה משמעותי, VPS עם GPU מחובר הוא הפתרון הכן. הוספת ליבות CPU למודל 8B עוזרת הרבה פחות ממה שאנשים מצפים.
כלל אחד תקף ללא קשר למה שתבחרו: לעולם אל תערבבו מודלי הטמעה בתוך אוסף אחד. שני מודלים שונים שבאופן מקרי חולקים את אותו רוחב מייצרים וקטורים שאינם ברי-השוואה. ההכנסה תצליח, החיפוש יחזיר שורות, והשורות יהיו שגויות, מבלי ששום דבר ידווח על שגיאה.
גיבויים: קיימים שני מסדי נתונים, לא אחד
הטעות הנפוצה ביותר בגיבוי mem0 היא ביצוע dump למסד נתונים יחיד. init-db.sh יוצר את mem0_app לצד מסד הנתונים המוגדר כברירת מחדל postgres, והם מכילים נתונים שונים. מסד הנתונים postgres מכיל את אוספי ה-pgvector, שהם הזיכרונות עצמם. mem0_app מכיל משתמשים, סשנים, מפתחות API ולוגים של בקשות.
שחזור של postgres בלבד יחזיר את הזיכרונות, אך כל חשבון ומפתח API יאבדו, כך שלא ניתן יהיה לבצע אימות כדי לקרוא אותם. בצעו dump לשניהם, בתוספת התפקידים (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גיבוי שמעולם לא שוחזר הוא בגדר ניחוש בלבד. ברגע שה-dumps תקינים, העבירו אותם מחוץ לשרת באמצעות צילומי מצב של 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."} בעת ביצוע add אומר שהבקשה לא כללה אף אחד מהם. זיכרון חייב להיות משויך למשהו, כיוון שמסנני החיפוש פועלים בדיוק על שדות אלו.
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 אומר שהאוסף נוצר ברוחב מסוים, אך ה-embedder מחזיר רוחב אחר. הגדירו embedding_model_dims ב-vector store והשתמשו ב-collection_name חדש.
החיפוש מחזיר שורות לא הגיוניות לאחר החלפת מודל, ללא הודעת שגיאה. הרוחב עדיין תואם ולכן מסד הנתונים תקין, אך שני מודלים שונים ממקמים את אותו משפט בנקודות שונות במרחב. התחילו אוסף חדש ובצעו הוספה מחדש.
Connection refused בלוגים של mem0 בעת ניסיון גישה ל-Ollama מציין בדרך כלל 127.0.0.1 ב-openai_base_url. בתוך המכולה, כתובת זו מתייחסת למכולה עצמה. השתמשו בשם השירות או ב-host gateway כאשר Ollama רץ על המארח.
504 Gateway Time-out מ-nginx בעת ביצוע add אומר שהמודל לקח זמן רב יותר מ-proxy_read_timeout. הגדילו את הערך, ובדקו אם הזיכרון נכתב בכל זאת לפני שתנסו שוב את הבקשה.
exit code 137 במהלך docker compose up --build הוא ה-out-of-memory killer שעוצר את בניית ה-dashboard. הוסיפו swap, או בנו את ה-image על מכונה חזקה יותר ודחפו אותה ל-registry.
error: port 3000 is already in use מגיע מ-target ה-make up של ה-repo, שמסרב לעלות כאשר פורטים 3000 או 8888 תפוסים. מצאו את התהליך התופס אותם בעזרת lsof -iTCP:3000 -sTCP:LISTEN.
FAQ
האם אני עדיין זקוק ל-Neo4j כדי להריץ את mem0 עם זיכרון גרפי?
לא. האלגוריתם החדש לניהול זיכרון, ששוחרר באפריל 2026, הסיר את מפתחות התצורה graph_store ו-enable_graph מגרסת ה-SDK בקוד פתוח. חילוץ ישויות מתבצע כעת במהלך פעולת הוספה רגילה ונכתב לאוסף pgvector שני בשם <collection_name>_entities, כך שאין צורך במסד נתונים גרפי חיצוני, אין מכולה נוספת ואין שלב הגירה. המחיר הוא שהשדה relations בתוצאות חיפוש אינו קיים עוד. ישויות כעת מעלות את הדירוג של זיכרון מסוים במקום לספק קשתות למעבר, לכן יישום שהסתמך על מעבר בין קשרים אלו זקוק למאגר גרפי משלו מחוץ ל-mem0.
מהו ה-VPS הקטן ביותר שמריץ שרת mem0 באירוח עצמי?
כאשר מודל השפה מאוחסן בשרת חיצוני, 2 GB של RAM וכ-4 GB של שטח דיסק פנוי מספיקים עבור מכולת ה-API, Postgres ולוח הבקרה. הרגע הקריטי הוא הבנייה הראשונה, כיוון שקומפילציית לוח הבקרה ב-Next.js צורכת יותר זיכרון מאשר הרצתו, ושרת עם 1 GB יגרום להריגת תהליך הבנייה עם exit code 137. אם Ollama רץ על אותו שרת, יש לתכנן את המשאבים בהתאם למודל: מודל 8B בקוונטיזציה של 4-bit דורש בערך 6 GB בעצמו, לכן תכננו על 8 GB.
האם ניתן להריץ את mem0 ללא מפתח API של OpenAI?
כן, דרך נקודת הקצה התואמת ל-OpenAI של Ollama. הגדרת "provider": "ollama" תיכשל, כיוון שתמונת השרת כוללת רק את הספריות openai, anthropic ו-gemini ומחזירה HTTP 400. במקום זאת, שמרו על "provider": "openai" והגדירו את "openai_base_url": "http://ollama:11434/v1" עם כל ערך api_key שאינו ריק, עבור ה-llm וה-embedder כאחד. Ollama מתעלם מהמפתח, ובדיקת הספק המצורפת עוברת כיוון שהספק הוא אכן openai.
מדוע mem0 לא מחזיר תוצאות לאחר מעבר למודל הטמעה (embedding) מקומי?
מכיוון שטבלת ה-pgvector נוצרה ברוחב קבוע. embedding_model_dims מוגדר כברירת מחדל ל-1536, nomic-embed-text מחזיר 768, ו-Postgres דוחה את פעולת ה-insert עם expected 1536 dimensions, not 768. mem0 יוצר את הטבלה עם CREATE TABLE IF NOT EXISTS, לכן שינוי המספר בלבד אינו משפיע על אוסף קיים. הגדירו את embedding_model_dims לרוחב האמיתי של המודל שלכם, אשרו את הרוחב על ידי קריאה ל-/v1/embeddings וספירת הערכים שהוא מחזיר, וספקו למאגר הווקטורים collection_name חדש באותו הזמן.