mem0 auf dem VPS selbst hosten: RAM-Bedarf und Setup
Erfahren Sie, wie viel RAM mem0 wirklich braucht, wie Compose nur an localhost bindet, TLS vor der API läuft und Ollama lokal eingebunden wird.
Was Self-Hosting von mem0 auf einem VPS tatsächlich an RAM kostet
Self-Hosting von mem0 bedeutet, drei Container zu betreiben: den FastAPI-Memory-Server, Postgres mit der Erweiterung pgvector und ein Next.js-Dashboard. mem0 ist eine Memory-Schicht für Agents. Sie senden eine Konversation dorthin. Ein Sprachmodell extrahiert die dauerhaften Fakten aus dieser Konversation. Diese Fakten werden als Vektoren gespeichert, damit eine spätere Abfrage die relevanten Fakten wieder abrufen kann.
Planen Sie für die drei Container etwa 1 GB residenten Arbeitsspeicher ein. Nach dem Erstellen der Images benötigen sie 3 bis 4 GB Speicherplatz. Ein VPS mit 2 GB RAM führt dies problemlos aus, wenn das Sprachmodell an einem anderen Ort läuft. Wenn das Modell auf demselben System über Ollama läuft, benötigt es deutlich mehr Ressourcen als alle anderen Komponenten: Ein auf 4 Bit quantisiertes 8B-Modell benötigt allein etwa 6 GB. Eine vollständig lokale Installation beginnt daher bei 8 GB RAM.
Übernehmen Sie diese Werte nicht aus einem Blogbeitrag, auch nicht aus diesem. Messen Sie den Stack, den Sie tatsächlich eingerichtet haben.
docker compose ps
docker stats --no-stream
docker system df -vdocker stats gibt den residenten Speicherverbrauch pro Container aus. docker system df -v gibt an, wie viel Speicherplatz jedes Image und jedes Volume belegt.
Der laufende Betrieb entspricht nicht dem Spitzenverbrauch. docker compose up -d --build kompiliert das Next.js-Dashboard. Dieser Node-Build ist der speicherintensivste Moment der gesamten Installation. Auf einem VPS mit 1 GB RAM beendet der Out-of-Memory-Killer des Kernels den Prozess. Der Build endet mit exit code 137. Bestätigen Sie die Ursache, bevor Sie nach einem Docker-Fehler suchen:
dmesg -T | grep -i "killed process"Wenn ein Server für Ihre Anforderungen zu aufwendig ist, gibt es tatsächlich kleinere Optionen. Ein lokaler Agent-Memory-Store ganz ohne Server und Memory, das direkt in Claude Code gespeichert wird benötigen beide keine Datenbank. Kehren Sie hierher zurück, wenn mehrere Agents oder mehrere Systeme auf dieselben Memories zugreifen müssen.
Benötige ich Neo4j für den Graph-Speicher von mem0?
Nein. Wenn eine Anleitung Sie auffordert, einen Neo4j-Container hinzuzufügen, ist sie älter als der aktuelle Code.
Graph-Speicher in mem0 bedeutete früher eine externe Graphdatenbank. Sie wurde unter einem graph_store-Schlüssel konfiguriert, wobei enable_graph auf true gesetzt wurde. Der neue Speicheralgorithmus, der im April 2026 veröffentlicht wurde, hat beide Schlüssel aus dem Open-Source-SDK entfernt. Die Entitätsextraktion läuft jetzt innerhalb des normalen Add-Pfads. Die Entitäten werden in eine zweite pgvector-Sammlung geschrieben, deren Name sich aus dem Namen der Hauptsammlung mit angehängtem _entities ergibt. Eine Migration ist nicht erforderlich. Die integrierte Verknüpfung von Entitäten beginnt beim nächsten Add-Aufruf zu funktionieren.
Wenn Sie den Graph-Speicher entfernen, sparen Sie einen JVM-Container, dessen Heap und mehrere hundert Megabyte beim Image. Bei einem VPS mit 2 GB kann das den Unterschied zwischen normalem Betrieb und Swapping ausmachen.
Sie verzichten dabei auf Folgendes. Die Suchergebnisse enthielten früher ein relations-Feld, das die Kanten zwischen Entitäten auflistete. Dieses Feld wurde entfernt. Treffer für Entitäten erhöhen jetzt die Position einer Erinnerung im kombinierten Score. Eine durchlaufbare Struktur gibt es nicht mehr. Wenn Ihre Anwendung diese Beziehungen verwendet hat, speichert mem0 sie nicht mehr. Sie müssen dann außerhalb von mem0 eine eigene Graphdatenbank betreiben und sie über Ihren eigenen Code mit Daten versorgen.
Die Compose-Datei im Repository ist für die Entwicklung vorgesehen.
server/docker-compose.yaml deklariert name: mem0-dev, und genau das tut sie auch. Lesen Sie die Datei, bevor Sie sie ausführen, denn für einen Server sind darin fünf Dinge falsch.
- Sie erstellt das Image aus
server/dev.Dockerfileund bindet Ihr Checkout mit.:/appüber das Image ein. Der Container führt dadurch aus, was in diesem Verzeichnis liegt, und nicht das, was Sie erstellt haben. - Ihr Befehl lautet
rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Dadurch wirdmem0aibei jedem Start erneut von PyPI installiert. Die Version, mit der Ihr Server ausgeführt wird, kann sich daher bei einem Neustart ändern, den Sie nicht als Upgrade geplant haben. - Derselbe pip-Schritt führt dazu, dass ein Neustart ohne ausgehenden Netzwerkzugriff fehlschlägt, bevor uvicorn überhaupt startet. Ihr Memory-Server ist dann nicht verfügbar, weil PyPI nicht erreichbar war.
--reloadstartet den Datei-Watcher von uvicorn. Dieser startet den Prozess neu, wenn Sie den Code bearbeiten. In der Produktion verbraucht er Speicher und einen zweiten Prozess, ohne einen sinnvollen Zweck zu erfüllen. Die Produktions-Dockerfileenthält--reloadebenfalls in ihremCMD. Sie überschreiben den Befehl daher in beiden Fällen.- Die veröffentlichten Ports sind
"8888:8000","8432:5432"und"3000:3000". Ein veröffentlichter Port ohne vorangestellte Adresse bindet an0.0.0.0. Dadurch ist Postgres auf 8432 sofort aus dem öffentlichen Internet erreichbar, sobald der Stack startet.
Dieser letzte Punkt verdient eine eigene Warnung. Docker veröffentlicht einen Port, indem es seine eigenen Regeln vor der von ufw verwalteten Chain einfügt. Daher schließt ufw deny 8432 keinen veröffentlichten Container-Port. Docker veröffentlicht Ports direkt an ufw vorbei erläutert die beteiligten Regeln.
Eine Compose-Datei für einen echten Server
Arbeiten Sie in server/, lassen Sie init-db.sh an dieser Stelle und ersetzen Sie docker-compose.yaml durch Folgendes.
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: bridgeHier sind fünf Änderungen relevant. Jede davon hat einen bestimmten Grund.
Jeder Eintrag ports beginnt mit 127.0.0.1. Dadurch akzeptiert der Kernel diese Verbindungen nur vom Server selbst. Alles von außerhalb kommt über den Reverse Proxy. Nur dieser verwaltet ein Zertifikat.
Postgres enthält überhaupt keinen Block ports. Der Container mem0 erreicht Postgres über mem0_network anhand des Servicenamens. Die Veröffentlichung von 8432 bringt Ihnen daher nichts und öffnet lediglich einen zusätzlichen Port. Verwenden Sie docker compose exec postgres psql -U postgres, wenn Sie eine Shell benötigen.
Der Verlauf wird vom ./history-Bind-Mount in ein Named Volume verschoben. Ein Bind-Mount bindet die Daten an einen Pfad und eine uid auf diesem Host. Ein Named Volume ist dagegen ein Docker-Objekt, das Docker als Snapshot sichern und verschieben kann. Named Volumes im Vergleich zu Bind-Mounts erläutert, wann welche Variante geeignet ist.
Der Befehl entfernt --reload und behält alembic upgrade head bei. Behalten Sie diesen Migrationsschritt bei. Ohne ihn startet die Anwendung mit einer Datenbank ohne Tabellen. Jede Anfrage schlägt dann bei der ersten Abfrage fehl.
NEXT_PUBLIC_API_URL ist die URL, die Ihr Browser aufruft. Daher muss sie auf die öffentliche HTTPS-Adresse und nicht auf http://mem0:8000 zeigen. Next.js bettet jeden Wert von NEXT_PUBLIC_ beim Build ein. Eine Änderung erfordert daher docker compose up -d --build mem0-dashboard. Ein einfacher Neustart verwendet weiterhin den alten, in JavaScript eingebetteten Wert. Das Dashboard ruft dann den falschen Host auf.
Secrets liegen in .env, und .env bleibt aus dem Internet erreichbar
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 .envSetzen Sie POSTGRES_PASSWORD, JWT_SECRET und ADMIN_API_KEY. Lassen Sie AUTH_DISABLED=false unverändert. Der Name beschreibt ehrlich, was dieses Flag bewirkt: Ist es aktiviert, gibt der Server den gesamten von ihm gehaltenen Speicher an jeden heraus, der den Port erreichen kann. Setzen Sie MEM0_TELEMETRY=false, wenn das Onboarding-Ereignis nicht an einen übergeordneten Dienst gesendet werden soll.
ADMIN_API_KEY wird mit dem Header X-API-Key über secrets.compare_digest verglichen. Bei einer Übereinstimmung werden alle Datenbankabfragen übersprungen. Dabei handelt es sich um ein Root-Credential für die gesamte API. Behandeln Sie es entsprechend: nicht in der Shell-History, nicht in Git und nicht in eine Eingabeaufforderung einfügen. Compose-Env-Dateien und wo daraus Secrets durchsickern und API-Schlüssel aus dem Kontext eines Agents heraushalten gelten hier unmittelbar, weil Agents die Aufrufer dieses Servers sind.
Die aus env_file geladenen Werte befinden sich in der Container-Umgebung. docker inspect gibt sie vollständig aus. Jeder im docker-Gruppe kann sie lesen, und jeder in der docker-Gruppe hat auf dem Host praktisch Root-Rechte.
TLS vor die API setzen, statt Port 8888 zu öffnen
Die API antwortet auf 127.0.0.1:8888 und das Dashboard auf 127.0.0.1:3000. nginx beendet TLS (Transport Layer Security) auf Port 443 und leitet den Datenverkehr an beide Dienste weiter.
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 ist wichtiger, als es zunächst scheint. Ein Add-Aufruf wird blockiert, während das Sprachmodell die Konversation liest und Fakten extrahiert. Ein lokales 8B-Modell auf der CPU benötigt regelmäßig länger als der nginx-Standardwert von 60 Sekunden. Dann erhält der Aufrufer 504 Gateway Time-out, während das Modell noch arbeitet und der Speicher weiterhin geschrieben wird. Dadurch entsteht ein Speicher, von dem Ihnen gemeldet wurde, dass er fehlgeschlagen sei.
Sperren Sie den übrigen Datenverkehr mit einer standardmäßig ablehnenden ufw-Richtlinie und lassen Sie nur 22 und 443 offen. Stellen Sie das Zertifikat mit certbot unter Ubuntu 24.04 hinter nginx aus. Wenn der Server bereits andere Anwendungen mit Traefik für mehrere Compose-Anwendungen bereitstellt, fügen Sie mem0 diesem Router hinzu, statt einen zweiten Proxy zu installieren.
Smoke-Test: Einen Memory-Eintrag hinzufügen und wieder abrufen
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"}'Eine erfolgreiche Antwort ist ein JSON-Objekt mit einer results-Liste. Jeder Eintrag enthält eine id, den extrahierten memory-Text und "event": "ADD". Der aktuelle Algorithmus gibt nur ADD-Ereignisse zurück. UPDATE- und DELETE-Ereignisse wurden entfernt. Dass sie fehlen, ist daher kein Fehler.
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}'Die Information zu Postgres 17 sollte mit einem Score zurückgegeben werden. Übergeben Sie den Bezeichner innerhalb von filters, wie gezeigt. Ein user_id auf oberster Ebene funktioniert weiterhin. Der Server protokolliert Top-level user_id in /search is deprecated. Use filters={...} instead. jedes Mal, wenn Sie es verwenden.
Räumen Sie anschließend auf, damit die Testdaten reale Suchvorgänge nicht beeinflussen:
curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
-H "X-API-Key: $MEM0_KEY"Wenn die Suche weniger Zeilen zurückgibt als erwartet, prüfen Sie die Standardwerte, bevor Sie die Retrieval-Logik verantwortlich machen. In der aktuellen Version ist top_k standardmäßig auf 20 gesetzt, statt wie zuvor auf 100. threshold verwendet standardmäßig 0.1 statt keinen Wert, sodass schwache Treffer jetzt automatisch herausgefiltert werden. Sobald dies mit curl funktioniert, verwenden Sie dieselben Endpunkte für die Anbindung an einen Agenten, entweder direkt oder über einen MCP-Server, der auf demselben VPS läuft.
mem0 vollständig ohne OpenAI-Key ausführen
Beginnen Sie mit dem Blocker, weil Sie innerhalb der ersten fünf Minuten darauf stoßen werden. Das Server-Image enthält einen festen Satz von Provider-Bibliotheken, und /configure lehnt alles ab, was nicht dazugehört:
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.Sie müssen nichts neu bauen. Ollama stellt unter /v1 eine OpenAI-kompatible API bereit, die /v1/chat/completions und /v1/embeddings abdeckt. Der OpenAI-Provider von mem0 akzeptiert außerdem ein openai_base_url. Verweisen Sie mit diesem Key auf Ollama. Dann besteht die gebündelte Prüfung, weil der Provider tatsächlich openai ist. Nur die Adresse ändert sich.
Fügen Sie Ollama demselben Compose-Projekt hinzu:
ollama:
image: ollama/ollama
restart: unless-stopped
networks: [mem0_network]
ports:
- "127.0.0.1:11434:11434"
volumes:
- ollama_models:/root/.ollamaFügen Sie ollama_models: unter dem Top-Level-Schlüssel volumes: hinzu. Laden Sie anschließend ein Chat-Modell und ein Embedding-Modell:
docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-textWenn Ollama bereits als systemd-Unit auf dem Host läuft, wie unter Ollama direkt auf einem VPS ausführen, dürfen Sie den Container nicht auf 127.0.0.1:11434 verweisen lassen. Innerhalb des mem0-Containers ist 127.0.0.1 der mem0-Container. Geben Sie dem mem0-Service extra_hosts: ["host.docker.internal:host-gateway"]. Setzen Sie Environment="OLLAMA_HOST=0.0.0.0:11434" in einem systemd-Drop-in, damit Ollama auf einer vom Bridge-Netzwerk erreichbaren Adresse lauscht. Lassen Sie Port 11434 in der Firewall geschlossen.
Fragen Sie das Modell vor jeder Konfiguration nach seiner Embedding-Dimension
Dieser eine Schritt entscheidet, ob die Suche überhaupt funktioniert.
Der pgvector-Store von mem0 erstellt seine Tabelle mit einer festen Vektorbreite, vector vector(1536), weil embedding_model_dims standardmäßig 1536 verwendet, also die Breite von OpenAIs text-embedding-3-small. nomic-embed-text gibt 768 Werte zurück. Innerhalb von mem0 werden diese beiden Zahlen nicht verglichen. Die Abweichung wird daher beim ersten Insert von Postgres gemeldet:
expected 1536 dimensions, not 768Vertrauen Sie auch nicht auf die Zahl in diesem Abschnitt. Fragen Sie das Modell:
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']))"Damit wird die Breite ausgegeben, die Ihre Collection verwenden muss. Schreiben Sie die Konfiguration in eine Datei. Ein Postgres-Passwort über Shell-Quoting einzufügen, führt leicht dazu, dass Tippfehler in die Produktionsumgebung gelangen.
{
"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"Der zweite Aufruf liest die Konfiguration zurück. Damit prüfen Sie, ob der Schreibvorgang erfolgreich war. Wiederholen Sie anschließend den obigen Smoke-Test.
Vier Details in diesem JSON sind nicht offensichtlich. Jeder Fehler bei diesen Details verursacht ein anderes Problem.
api_key ist die Zeichenfolge ollama. Ollama ignoriert ihren Wert. Sie darf nicht leer sein, weil die OpenAI-Clientbibliothek bereits vor dem Absenden einer Anfrage eine Exception auslöst, wenn kein Key gesetzt ist. Jede nicht leere Zeichenfolge funktioniert.
embedding_model_dims gehört in den Vektorspeicher. Beim Embedder gibt es absichtlich kein embedding_dims. mem0 sendet den OpenAI-Parameter dimensions nur, wenn Sie embedding_dims setzen. Backends, die Matryoshka-Truncation nicht implementieren, lehnen diesen Parameter direkt ab. Setzen Sie die Breite beim Erstellen der Tabelle und lassen Sie den Embedder unverändert.
collection_name ist neu. mem0 erstellt seine Tabelle mit CREATE TABLE IF NOT EXISTS. Wenn Sie einer vorhandenen Collection eine andere Breite zuweisen, hat das keinerlei Wirkung: Die alte Spalte vector(1536) bleibt bestehen, und jeder Insert schlägt fehl. Für eine Änderung der Breite benötigen Sie einen neuen Collection-Namen oder müssen die alte Tabelle manuell löschen.
Der Host in openai_base_url ist der Compose-Service-Name ollama und nicht localhost. Container lösen sich in ihrem gemeinsamen Netzwerk über den Service-Namen auf.
Welche Kosten der vollständig lokale Pfad verursacht
Seien Sie bei der Qualität realistisch. Die veröffentlichten Benchmark-Werte von mem0 wurden mit Frontier-Modellen für die Extraktion ermittelt. Betrachten Sie sie daher als Obergrenze und nicht als Prognose für ein 8B-Modell auf Ihrem VPS. Ein kleines Modell formuliert Fakten vager. Es gibt außerdem gelegentlich Prosa zurück, obwohl JSON angefordert wurde. Das zeigt sich durch einen Add-Aufruf, der ohne Fehler eine leere results-Liste zurückgibt.
Geschwindigkeit ist der zweite Kostenfaktor. Die Extraktion benötigt bei reiner CPU-Nutzung mehrere Sekunden pro Add-Aufruf, und jede gespeicherte Nachricht verursacht diesen Aufwand. Ein Modell, das über das angeforderte JSON hinaus weiter schreibt, verschärft das Problem. Mit der Begrenzung der Antwort durch num_predict legen Sie daher eine Obergrenze für die Laufzeit jedes einzelnen Add-Aufrufs fest. Wenn diese Latenz relevant ist, ist ein VPS mit angeschlossener GPU die sachgerechte Lösung. Zusätzliche CPU-Kerne helfen bei einem 8B-Modell deutlich weniger als erwartet. Das Modell zu wechseln ist der kostengünstigere Ansatz als ein Wechsel der Maschine. Nemotron 3.5 Lightning auf einem VPS nennt den zu ladenden Tag, den benötigten RAM und die Frage, ob die reine CPU-Nutzung schnell genug ist.
Eine Regel gilt unabhängig von Ihrer Wahl: Verwenden Sie niemals verschiedene Embedding-Modelle innerhalb derselben Collection. Zwei verschiedene Modelle können dieselbe Breite haben und trotzdem nicht vergleichbare Vektoren erzeugen. Der Insert ist erfolgreich, die Suche gibt Zeilen zurück, und die Ergebnisse sind falsch. Irgendwo wird dabei kein Fehler gemeldet.
Backups: Es gibt zwei Datenbanken, nicht eine
Der häufigste Fehler beim Backup von mem0 besteht darin, nur eine Datenbank zu sichern. init-db.sh erstellt neben der Standarddatenbank postgres zusätzlich mem0_app. Beide enthalten unterschiedliche Daten. Die Datenbank postgres enthält die pgvector-Sammlungen, also die Erinnerungen. mem0_app enthält Benutzer, Sitzungen, API-Schlüssel und Anforderungsprotokolle. Jede selbst gehostete Anwendung strukturiert ihren Zustand auf eigene Weise. Deshalb benötigen zwei Fotoserver mit derselben Aufgabe trotzdem unterschiedliche Backup-Befehle. Prüfen Sie daher, welche Daten Ihre Anwendung speichert, bevor Sie einem Dump vertrauen. Am anderen Ende dieses Spektrums steht beispielsweise eine als Videothek der 90er-Jahre neu aufgebaute Jellyfin-Bibliothek. Sie bezieht ihren gesamten Katalog aus einem anderen Dienst. Deshalb muss meist nur ihre eigene Konfiguration kopiert werden. mem0 benötigt dagegen beide Datenbanken. Andernfalls ist die Wiederherstellung unbrauchbar.
Wenn Sie nur postgres wiederherstellen, kommen die Memories zurück, während alle Konten und API-Schlüssel verloren sind. Dann kann sich nichts authentifizieren, um auf die Memories zuzugreifen. Sichern Sie beide Datenbanken einschließlich der Rollen mit einem Befehl:
docker compose exec -T postgres pg_dumpall -U postgres --clean \
| gzip > "mem0-$(date +%F).sql.gz"Das History-Volume ist von PostgreSQL getrennt und benötigt eine eigene Kopie:
docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
alpine tar czf /backup/mem0-history.tgz -C /data .Docker versieht Volume-Namen mit dem Projektnamen. Prüfen Sie daher mit docker volume ls den tatsächlichen Namen, bevor Sie mem0_mem0_history verwenden.
Stellen Sie die Daten in einem temporären Container wieder her und prüfen Sie die Zeilenanzahl, bevor Sie dem Ergebnis vertrauen:
gunzip -c mem0-2026-08-03.sql.gz \
| docker compose exec -T postgres psql -U postgres -d postgresEin Backup, das Sie noch nie wiederhergestellt haben, ist nur eine Vermutung. Sobald die Dumps korrekt sind, übertragen Sie sie mit restic-Snapshots in einen externen Speicher vom Server. Ein Backup, das auf dem Server liegt, den es schützen soll, schützt nichts.
Fehlerfälle und die genau angezeigten Zeichenfolgen
{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} bedeutet, dass der Header fehlt oder falsch geschrieben ist. Der Name lautet X-API-Key, und curl sendet Header-Namen unverändert.
{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} bei einem Add-Vorgang bedeutet, dass die Anfrage keines dieser Felder enthielt. Eine Memory muss einem bestimmten Kontext zugeordnet sein, weil die Suche genau nach diesen Feldern filtert.
LLM provider 'ollama' is not bundled in this image mit HTTP 400 bedeutet, dass Sie "provider": "ollama" gesendet haben. Verwenden Sie "provider": "openai" und richten Sie openai_base_url auf Ollama.
expected 1536 dimensions, not 768 von Postgres bedeutet, dass die Collection mit einer bestimmten Breite erstellt wurde, der Embedder aber eine andere zurückgibt. Setzen Sie embedding_model_dims im Vector Store und verwenden Sie eine neue collection_name.
Die Suche liefert nach einem Modellwechsel unplausible Zeilen, ohne dass irgendwo ein Fehler auftritt. Die Breite stimmt weiterhin überein, daher akzeptiert die Datenbank die Werte. Zwei Modelle ordnen denselben Satz jedoch an unterschiedlichen Positionen an. Erstellen Sie eine neue Collection und fügen Sie die Daten erneut hinzu.
Connection refused in den mem0-Logs beim Zugriff auf Ollama bedeutet normalerweise 127.0.0.1 in openai_base_url. Innerhalb des Containers bezeichnet diese Adresse den Container selbst. Verwenden Sie den Servicenamen oder das Host-Gateway, wenn Ollama auf dem Host läuft.
504 Gateway Time-out von nginx bei einem Add-Vorgang bedeutet, dass das Modell länger als proxy_read_timeout benötigt hat. Erhöhen Sie den Wert und prüfen Sie vor einem erneuten Senden der Anfrage, ob die Memory möglicherweise bereits geschrieben wurde.
exit code 137 während docker compose up --build bedeutet, dass der Out-of-Memory-Killer den Dashboard-Build beendet. Fügen Sie Swap hinzu oder erstellen Sie das Image auf einer größeren Maschine und übertragen Sie es in eine Registry.
error: port 3000 is already in use stammt aus dem make up-Target des Repositorys. Dieses verweigert den Start, wenn 3000 oder 8888 bereits belegt sind. Ermitteln Sie den Besitzer mit lsof -iTCP:3000 -sTCP:LISTEN.
FAQ
Benötige ich weiterhin Neo4j, um mem0 mit Graph-Speicher zu betreiben?
Nein. Der neue Speicheralgorithmus, der im April 2026 veröffentlicht wurde, hat die Konfigurationsschlüssel graph_store und enable_graph aus dem Open-Source-SDK entfernt. Die Entitätsextraktion läuft nun während eines normalen Hinzufügens und schreibt in eine zweite pgvector-Sammlung namens <collection_name>_entities. Daher sind keine externe Graphdatenbank, kein zusätzlicher Container und keine Migration erforderlich. Der Nachteil ist, dass das Feld relations in den Suchergebnissen nicht mehr existiert. Entitäten erhöhen nun die Rangfolge eines Speichers, anstatt Kanten zum Traversieren bereitzustellen. Eine Anwendung, die diese Beziehungen durchläuft, benötigt daher außerhalb von mem0 einen eigenen Graphspeicher.
Welcher ist der kleinste VPS, auf dem ein selbst gehosteter mem0-Server läuft?
Wenn das Sprachmodell an einem anderen Ort gehostet wird, reichen 2 GB RAM und etwa 4 GB freier Speicherplatz für den API-Container, Postgres und das Dashboard aus. Eng wird es beim ersten Build, weil das Kompilieren des Next.js-Dashboards mehr Speicher benötigt als dessen Betrieb. Auf einem System mit 1 GB wird der Build mit exit code 137 beendet. Wenn Ollama auf demselben Server läuft, müssen Sie den Speicherbedarf des Modells zugrunde legen: Ein 8B-Modell mit 4-Bit-Quantisierung benötigt allein ungefähr 6 GB. Planen Sie daher 8 GB ein.
Kann ich mem0 ohne einen OpenAI-API-Schlüssel ausführen?
Ja, über Ollamas OpenAI-kompatiblen Endpunkt. Das Setzen von "provider": "ollama" schlägt fehl, weil das Server-Image nur die Bibliotheken openai, anthropic und gemini enthält und HTTP 400 zurückgibt. Belassen Sie stattdessen "provider": "openai" und setzen Sie "openai_base_url": "http://ollama:11434/v1" mit einem beliebigen nicht leeren api_key, sowohl für das llm als auch für den Embedder. Ollama ignoriert den Schlüssel. Die Prüfung des integrierten Providers ist erfolgreich, weil der Provider tatsächlich openai ist.
Warum liefert mem0 nach dem Wechsel zu einem lokalen Embedding-Modell keine Ergebnisse?
Weil die pgvector-Tabelle mit einer festen Vektordimension erstellt wurde. embedding_model_dims verwendet standardmäßig 1536, nomic-embed-text liefert 768, und Postgres lehnt das Einfügen mit expected 1536 dimensions, not 768 ab. mem0 erstellt die Tabelle mit CREATE TABLE IF NOT EXISTS. Daher ändert das alleinige Anpassen der Anzahl nichts an einer vorhandenen Sammlung. Setzen Sie embedding_model_dims auf die tatsächliche Dimension Ihres Modells. Bestätigen Sie diese Dimension, indem Sie /v1/embeddings aufrufen und die zurückgegebenen Werte zählen. Geben Sie dem Vektorspeicher gleichzeitig ein neues collection_name.