SSD Nodes Learn Hosting plans →
Anleitungen Matt ConnorVon Matt Connor · Aktualisiert 2026-10-26

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 -v

docker 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.

Der Graph-Speicher in mem0 bezeichnete 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-Collection geschrieben. Ihr Name entspricht dem Namen der Haupt-Collection mit angehängtem _entities. Es ist kein Einrichtungsschritt erforderlich. Diese Collection wird bei der ersten Verwendung erstellt. Die integrierte Entitätsverknüpfung funktioniert daher beim nächsten Add-Aufruf.

Durch den Verzicht auf den Graph-Speicher entfallen ein JVM-Container, dessen Heap und mehrere hundert Megabyte an Image-Größe. Auf einem VPS mit 2 GB RAM kann das den Unterschied zwischen normalem Betrieb und Swapping ausmachen.

Sie verzichten damit auf Folgendes. Früher enthielten die Suchergebnisse ein relations-Feld mit den Kanten zwischen Entitäten. Dieses Feld ist entfallen. Treffer für Entitäten erhöhen jetzt die Position eines Memory-Eintrags im kombinierten Score. Eine durchlaufbare Struktur gibt es nicht mehr. Wenn Ihre Anwendung diese Beziehungen bisher durchlaufen hat, speichert mem0 sie nicht mehr. Sie müssen dann außerhalb von mem0 eine eigene Graphdatenbank betreiben und sie mit Ihrem eigenen Code befüllen.

Die Compose-Datei im Repository ist für die Entwicklung gedacht

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ünf Punkte darin sind für einen Server ungeeignet.

  • Sie erstellt das Image aus server/dev.Dockerfile und bindet mit .:/app Ihr Checkout-Verzeichnis über das Image. Der Container verwendet dadurch den Inhalt dieses Verzeichnisses und nicht den Inhalt des erstellten Images.
  • Der Befehl lautet 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. Dadurch wird mem0ai bei jedem Start erneut von PyPI installiert. Die auf dem Server ausgeführte Version kann sich daher während eines Neustarts ändern, den Sie nicht als Upgrade eingeplant haben.
  • Derselbe pip-Schritt führt dazu, dass ein Neustart ohne ausgehende Netzwerkverbindung fehlschlägt, bevor uvicorn überhaupt startet. Ihr Memory-Server ist dann nicht verfügbar, weil PyPI nicht erreichbar war.
  • --reload aktiviert den Dateiüberwacher von uvicorn. Er startet den Prozess neu, wenn Sie den Code bearbeiten. In der Produktion ist er nutzlos und verbraucht Speicher sowie einen zweiten Prozess. Die Produktions-Dockerfile enthält --reload ebenfalls in ihrem CMD. Sie müssen den Befehl daher in jedem Fall überschreiben.
  • Die veröffentlichten Ports sind "8888:8000", "8432:5432" und "3000:3000". Ein veröffentlichter Port ohne vorangestellte Adresse bindet 0.0.0.0. Postgres ist dadurch sofort nach dem Start des Stacks über Port 8432 aus dem öffentlichen Internet erreichbar.

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 einen veröffentlichten Container-Port nicht. Docker veröffentlicht Ports direkt an ufw vorbei erläutert die beteiligten Regeln.

Eine Compose-Datei für einen echten Server

Arbeiten Sie innerhalb von 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: bridge

Hier sind fünf Änderungen relevant. Jede davon hat einen bestimmten Grund.

Jeder Eintrag für ports beginnt mit 127.0.0.1. Der Kernel akzeptiert diese Verbindungen daher nur vom Server selbst. Alles von außerhalb kommt über den Reverse Proxy. Nur dort liegt ein Zertifikat.

Postgres hat überhaupt keinen Block ports. Der mem0-Container erreicht den Dienst über mem0_network anhand des Servicenamens. Das Veröffentlichen von 8432 bringt Ihnen daher nichts und öffnet lediglich einen weiteren Port. Verwenden Sie docker compose exec postgres psql -U postgres, wenn Sie eine Shell benötigen.

Die Historie wechselt vom ./history-Bind-Mount zu einem benannten Volume. Ein Bind-Mount bindet die Daten an einen Pfad und eine UID auf diesem Host. Ein benanntes Volume ist dagegen ein Docker-Objekt, das Docker als Snapshot speichern und verschieben kann. Benannte Volumes und 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, und jede Anfrage schlägt bei der ersten Abfrage fehl.

NEXT_PUBLIC_API_URL ist die URL, die Ihr Browser aufruft. Daher muss es die öffentliche HTTPS-Adresse und nicht http://mem0:8000 sein. Next.js fügt NEXT_PUBLIC_-Werte zum Build-Zeitpunkt direkt ein. Das Image dieses Dashboards setzt stattdessen einen Platzhalter ein. Der Entrypoint ersetzt ihn beim Start des Containers durch den tatsächlichen Wert des Containers. Eine Änderung erfordert daher docker compose up -d mem0-dashboard, wodurch der Container neu erstellt wird, aber keinen neuen Build. docker compose restart mem0-dashboard verwendet den alten Container erneut. Dessen Dateien enthalten weiterhin den alten Wert, sodass das Dashboard weiterhin den falschen Host aufruft.

Secrets stehen in .env, und .env bleibt außerhalb des Internets

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

Setzen Sie POSTGRES_PASSWORD, JWT_SECRET und ADMIN_API_KEY. Lassen Sie AUTH_DISABLED=false unverändert. Der Name beschreibt genau, was diese Option bewirkt: Ist sie aktiviert, übergibt der Server den gesamten von ihm gehaltenen Speicher an jeden, 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 unter Verwendung von secrets.compare_digest verglichen. Bei einer Übereinstimmung entfällt die schlüsselbezogene Abfrage in der Datenbank vollständig. Die Anfrage wird dann als der erste auf dem Server angelegte Benutzer ausgeführt. Damit handelt es sich um ein Root-Credential für die gesamte API. Behandeln Sie es entsprechend: nicht in der Shell-History speichern, nicht in Git ablegen und nicht in eine Eingabeaufforderung einfügen. Compose-Umgebungsdateien und wo daraus Secrets nach außen gelangen und API-Keys aus dem Kontext eines Agenten heraushalten gelten hier unmittelbar, weil Agenten die Clients dieses Servers sind.

Aus env_file geladene Werte befinden sich in der Container-Umgebung, und docker inspect gibt sie vollständig aus. Jeder im docker-Gruppe kann sie lesen. Jeder im docker-Gruppe hat auf dem Host effektiv 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 die TLS-Verbindung (Transport Layer Security) auf Port 443 und leitet die Anfragen 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|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;
    }
}

Der Browser-Code des Dashboards ruft jeden API-Pfad in diesem Muster unter NEXT_PUBLIC_API_URL auf, einschließlich /entities und /requests. Wenn ein Pfad fehlt, leitet nginx den Aufruf an Next.js weiter. Die Dashboard-Seite erhält dann statt der Daten den Status 404.

proxy_read_timeout ist wichtiger, als es zunächst wirkt. 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 das nginx-Standardlimit von 60 Sekunden. Der Aufrufer erhält dann 504 Gateway Time-out, obwohl das Modell noch arbeitet und der Speicher weiterhin geschrieben wird. Dadurch entsteht ein Speicher, obwohl Ihnen ein Fehler gemeldet wurde.

Sperren Sie den restlichen Zugriff mit einer standardmäßigen deny-Richtlinie für ufw und lassen Sie nur 22, 80 und 443 offen. Stellen Sie das Zertifikat mit certbot unter Ubuntu 24.04 hinter nginx aus. Port 80 muss erreichbar bleiben, weil dieser Ablauf die HTTP-01-Challenge verwendet, die Let's Encrypt bei der Ausstellung und anschließend bei jeder Verlängerung ausschließlich über Port 80 ausführt. Wenn Port 80 gesperrt bleibt, müssen Sie auf eine DNS-01-Challenge umstellen. Wenn der Server bereits andere Anwendungen mit Traefik für das Routing mehrerer 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 ganz ohne OpenAI-Schlüssel ausführen

Beginnen Sie mit dem Blocker. Sie werden in den ersten fünf Minuten darauf stoßen. Das Server-Image enthält einen festen Satz von Provider-Bibliotheken, und /configure lehnt alles ab, was nicht darin enthalten ist:

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 erstellen. Ollama stellt unter /v1 eine OpenAI-kompatible API bereit. Sie unterstützt /v1/chat/completions und /v1/embeddings. Der OpenAI-Provider von mem0 akzeptiert außerdem ein openai_base_url. Verweisen Sie mit diesem Schlüssel 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/.ollama

Fügen Sie ollama_models: unter dem Schlüssel volumes: der obersten Ebene hinzu. Laden Sie anschließend ein Chatmodell und ein Embedding-Modell herunter:

docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-text

Wenn 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. Innerhalb des mem0-Containers ist 127.0.0.1 der mem0-Container. Geben Sie dem mem0-Dienst 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. Halten Sie Port 11434 in der Firewall geschlossen.

Fragen Sie das Modell vor jeder Konfiguration nach seiner Embedding-Dimension

Dieser Schritt entscheidet, ob der Abruf überhaupt funktioniert.

Der pgvector-Speicher von mem0 erstellt seine Tabelle mit einer festen Vektorbreite, vector vector(1536), weil embedding_model_dims standardmäßig auf 1536 gesetzt ist, 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 768

Verlassen Sie sich 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 durch Shell-Quoting einzufügen, führt sonst 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 oben beschriebenen Smoke-Test.

Vier Details in diesem JSON sind nicht offensichtlich. Jeder Fehler an dieser Stelle verursacht ein eigenes Problem.

api_key ist die Zeichenfolge ollama. Ollama ignoriert ihren Wert. Sie darf nicht leer sein, weil die OpenAI-Clientbibliothek bereits vor dem ersten Request einen Fehler auslöst, wenn kein Schlüssel gesetzt ist. Jede nicht leere Zeichenfolge funktioniert.

embedding_model_dims gehört in den Vektorspeicher. Für den Embedder gibt es absichtlich kein embedding_dims. mem0 sendet den OpenAI-Parameter dimensions nur, wenn Sie embedding_dims setzen. Backends, die Matryoshka-Trunkierung nicht unterstützen, 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 eine andere Breite auf eine vorhandene Collection anwenden, passiert überhaupt nichts: Die alte Spalte vector(1536) bleibt bestehen und jedes Insert schlägt fehl. Für eine Änderung der Breite benötigen Sie einen neuen Collection-Namen. Alternativ können Sie die alte Tabelle manuell löschen.

Der Host in openai_base_url ist der Compose-Dienstname ollama und nicht localhost. Container lösen einander in ihrem gemeinsamen Netzwerk über den Dienstnamen auf.

Welche Kosten der vollständig lokale Betrieb verursacht

Seien Sie bei der Qualität realistisch. Die veröffentlichten Benchmark-Werte von mem0 stammen von der verwalteten Plattform. Laut README enthält sie proprietäre Optimierungen, die im Open-Source-SDK nicht verfügbar sind. Außerdem läuft sie auf einem Modell-Stack, den mem0 nicht nennt. In der Forschungsarbeit hinter den ursprünglichen LOCOMO-Ergebnissen wurde jeder Modellaufruf mit GPT-4o-mini ausgeführt, einem gehosteten Modell. Betrachten Sie diese Werte daher als Obergrenze und nicht als Prognose für ein 8B-Modell auf Ihrem VPS. Ein kleines Modell formuliert ungenauere Fakten. Manchmal gibt es außerdem Text zurück, obwohl JSON angefordert wurde. Das zeigt sich als Add-Aufruf, der ohne Fehler eine leere Liste results zurückgibt.

Geschwindigkeit ist der zweite Kostenfaktor. Die Extraktion benötigt bei reiner CPU-Auslastung mehrere Sekunden pro Add-Aufruf. Jede gespeicherte Nachricht verursacht diese Kosten. Ein Modell, das über das angeforderte JSON hinaus weiter ausführt, verschlimmert das Problem. Mit num_predict zum Begrenzen der Antwort 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. Mehr CPU-Kerne helfen bei einem 8B-Modell deutlich weniger als erwartet. Das Modell zu ändern ist günstiger als die Maschine zu ändern. Nemotron 3.5 Lightning auf einem VPS nennt das zu ladende Tag, den benötigten Arbeitsspeicher und ob die reine CPU-Ausführung schnell genug ist.

Eine Regel gilt unabhängig von Ihrer Auswahl: Mischen Sie niemals Embedding-Modelle innerhalb einer Collection. Zwei verschiedene Modelle können dieselbe Breite haben und trotzdem nicht vergleichbare Vektoren erzeugen. Das Insert ist erfolgreich, die Suche gibt Zeilen zurück und die Ergebnisse sind falsch. Es gibt an keiner Stelle eine Fehlermeldung.

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 postgres

Ein 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.

Fehlermodi und die exakten Meldungen, die Sie sehen werden

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} bedeutet, dass der Header fehlt oder falsch geschrieben ist. Der Name ist X-API-Key, und curl sendet Headernamen wortwörtlich.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} bei einem Add bedeutet, dass die Anfrage keinen dieser Bezeichner (user_id, agent_id, run_id) enthielt. Ein Speicher muss einem Objekt zugeordnet sein, da die Suche exakt 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" mit openai_base_url, das auf Ollama zeigt.

expected 1536 dimensions, not 768 von Postgres bedeutet, dass die Collection mit einer bestimmten Breite erstellt wurde, der Embedder jedoch eine andere zurückgibt. Setzen Sie embedding_model_dims im Vektorspeicher und verwenden Sie eine neue collection_name.

Die Suche liefert nach einem Modellwechsel unsinnige Zeilen, ohne dass ein Fehler auftritt. Die Breite stimmt weiterhin überein, daher ist die Datenbank konsistent, aber zwei Modelle platzieren denselben Satz an unterschiedlichen Positionen. Erstellen Sie eine neue Collection und fügen Sie die Daten erneut hinzu.

Connection refused in den mem0-Logs beim Erreichen von Ollama bedeutet meist 127.0.0.1 in openai_base_url. Innerhalb des Containers ist diese Adresse der Container selbst. Verwenden Sie den Dienstnamen oder das Host-Gateway, wenn Ollama auf dem Host läuft.

504 Gateway Time-out von nginx bei einem Add bedeutet, dass das Modell länger als proxy_read_timeout benötigt hat. Erhöhen Sie diesen Wert und prüfen Sie, ob der Speicher dennoch geschrieben wurde, bevor Sie die Anfrage wiederholen.

exit code 137 während docker compose up --build bedeutet, dass ein Prozess mit SIGKILL beendet wurde, da ein fataler Signalwert N als 128 + N gemeldet wird und SIGKILL das Signal 9 ist. Während das Dashboard auf einem kleinen VPS erstellt wird, ist der wahrscheinlichste Verursacher der Out-of-Memory-Killer. Führen Sie daher den dmesg-Check aus dem ersten Abschnitt durch, bevor Sie Maßnahmen ergreifen. Wenn dieser einen beendeten Prozess anzeigt, fügen Sie Swap hinzu oder erstellen Sie das Image auf einer größeren Maschine und pushen Sie es in eine Registry.

error: port 3000 is already in use stammt vom make up-Target des Repos, das den Start verweigert, wenn 3000 oder 8888 belegt sind. Finden Sie den Besitzer mit lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Benötige ich weiterhin Neo4j, um mem0 mit Graph-Memory auszuführen?

Nein. Der neue Memory-Algorithmus, 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 jetzt während eines normalen Add-Vorgangs und schreibt in eine zweite pgvector-Collection namens <collection_name>_entities. Daher sind keine externe Graphdatenbank, kein zusätzlicher Container und kein Einrichtungsschritt erforderlich. Der Nachteil ist, dass das Feld relations in den Suchergebnissen nicht mehr vorhanden ist. Entitäten verbessern jetzt das Ranking eines Memory-Eintrags, anstatt durchlaufbare Kanten bereitzustellen. Eine Anwendung, die diese Beziehungen durchlaufen hat, 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 anderer Stelle 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 sein 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. Behalten Sie stattdessen "provider": "openai" bei und setzen Sie "openai_base_url": "http://ollama:11434/v1" mit einem beliebigen nicht leeren api_key, sowohl für den LLM als auch für den Embedder. Ollama ignoriert den Schlüssel. Die gebündelte Provider-Prüfung wird bestanden, weil der Provider tatsächlich openai ist.

Warum liefert mem0 keine Ergebnisse mehr, nachdem ich auf ein lokales Embedding-Modell umgestellt habe?

Weil die pgvector-Tabelle mit einer festen Dimension erstellt wurde. embedding_model_dims ist standardmäßig auf 1536 gesetzt, nomic-embed-text liefert 768, und Postgres lehnt den Insert mit expected 1536 dimensions, not 768 ab. mem0 erstellt die Tabelle mit CREATE TABLE IF NOT EXISTS. Daher ändert das alleinige Anpassen der Zahl an einer bestehenden Collection nichts. Setzen Sie embedding_model_dims auf die tatsächliche Dimension Ihres Modells. Prüfen Sie diese Dimension, indem Sie /v1/embeddings aufrufen und die zurückgegebenen Werte zählen. Geben Sie dem Vektorspeicher gleichzeitig eine neue collection_name.

War dieser Artikel hilfreich?