Auto-héberger mem0 sur un VPS avec Ollama
Découvrez le vrai minimum de RAM pour mem0, un fichier Compose lié à localhost, TLS devant l’API et une installation locale avec Ollama.
Ce que coûte réellement l’auto-hébergement de mem0 en RAM sur un VPS
L’auto-hébergement de mem0 consiste à exécuter trois conteneurs : le serveur FastAPI de mémoire, Postgres avec l’extension pgvector et un dashboard Next.js. mem0 est une couche de mémoire pour les agents. Vous lui envoyez une conversation, un modèle de langage en extrait les faits durables, puis ces faits sont stockés sous forme de vecteurs afin qu’une requête ultérieure puisse récupérer ceux qui sont pertinents.
Prévoyez environ 1 GB de mémoire résidente pour les trois conteneurs et 3 à 4 GB d’espace disque une fois les images construites. Un VPS de 2 GB exécute cet ensemble sans difficulté lorsque le modèle de langage fonctionne ailleurs. Si le modèle fonctionne sur le même serveur avec Ollama, il consomme beaucoup plus que le reste : un modèle 8B quantifié sur 4 bits nécessite environ 6 GB à lui seul. Une installation entièrement locale nécessite donc au moins 8 GB.
Ne reprenez pas ces chiffres d’un article de blog, y compris celui-ci. Mesurez la stack que vous avez réellement construite.
docker compose ps
docker stats --no-stream
docker system df -vdocker stats affiche la mémoire résidente de chaque conteneur. docker system df -v affiche l’espace disque occupé par chaque image et chaque volume.
La consommation en régime permanent n’est pas le pic. docker compose up -d --build compile le dashboard Next.js, et cette compilation Node est le moment le plus gourmand de toute l’installation. Sur un VPS de 1 GB, l’out-of-memory killer du kernel l’arrête et la compilation se termine par exit code 137. Confirmez la cause avant de chercher un bug Docker :
dmesg -T | grep -i "killed process"Si un serveur vous semble disproportionné par rapport à vos besoins, les options plus légères sont bien réelles. un stockage de mémoire local pour agent, sans serveur et une mémoire intégrée directement à Claude Code évitent tous deux la base de données. Revenez à cette solution lorsque plusieurs agents ou plusieurs machines doivent lire les mêmes mémoires.
Faut-il utiliser Neo4j pour la mémoire de graphe de mem0 ?
Non. Si un guide vous demande d’ajouter un conteneur Neo4j, il est antérieur au code actuel.
La mémoire de graphe de mem0 désignait auparavant une base de données de graphe externe, configurée avec une clé graph_store et enable_graph définie sur true. Le nouvel algorithme de mémoire, publié en avril 2026, a supprimé ces deux clés du SDK open source. L’extraction des entités s’exécute désormais dans le chemin d’ajout normal. Les entités sont écrites dans une seconde collection pgvector, nommée d’après votre collection principale avec _entities ajouté à la fin. Aucune migration n’est nécessaire. La liaison intégrée des entités commence à fonctionner lors du prochain appel d’ajout.
La suppression du graph store évite d’exécuter un conteneur JVM, ainsi que de lui attribuer de la mémoire heap. Elle économise aussi plusieurs centaines de mégaoctets d’image. Sur un VPS de 2 GB, cela peut faire la différence entre un système qui fonctionne et un système qui utilise le swap.
Voici clairement ce que vous perdez. Les résultats de recherche contenaient auparavant un champ relations qui listait les relations entre les entités. Ce champ a disparu. Les correspondances d’entités augmentent désormais la position d’une mémoire dans le score combiné. Aucune structure ne peut être parcourue. Si votre application parcourait ces relations, mem0 ne les conserve plus. Vous devez alors gérer votre propre graph database en dehors de mem0 et l’alimenter avec votre propre code.
Le fichier compose du dépôt est un fichier compose de développement
server/docker-compose.yaml déclare name: mem0-dev, et c’est bien ce qu’il fait. Lisez-le avant de l’exécuter, car cinq éléments ne conviennent pas à un serveur.
- Il construit l’image à partir de
server/dev.Dockerfileet monte votre checkout par-dessus avec.:/app. Le conteneur exécute donc ce qui se trouve dans ce répertoire, et non ce que vous avez construit. - Sa commande est
rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Elle réinstallemem0aidepuis PyPI à chaque démarrage. La version exécutée par votre serveur peut donc changer lors d’un redémarrage que vous ne pensiez pas être une mise à niveau. - Cette étape pip signifie également qu’un redémarrage sans réseau sortant échoue avant même le lancement d’uvicorn. Votre serveur de mémoire est alors hors service parce que PyPI est inaccessible.
--reloadactive le watcher de fichiers d’uvicorn. Il sert à redémarrer le processus lorsque vous modifiez le code. En production, il consomme de la mémoire et lance un second processus sans utilité. LeDockerfilede production contient également--reloaddans sonCMD. Vous devez donc remplacer la commande dans les deux cas.- Les ports publiés sont
"8888:8000","8432:5432"et"3000:3000". Un port publié sans adresse devant lui est lié à0.0.0.0. Postgres est donc accessible depuis Internet sur le port 8432 dès le démarrage de la stack.
Ce dernier point mérite un avertissement spécifique. Docker publie un port en plaçant ses propres règles avant la chaîne gérée par ufw. Par conséquent, ufw deny 8432 ne ferme pas un port de conteneur publié. La publication directe des ports Docker en contournant ufw détaille les règles concernées.
Un fichier Compose pour un serveur réel
Travaillez dans server/, conservez init-db.sh à sa place et remplacez docker-compose.yaml par ceci.
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: bridgeCinq changements sont importants ici, et chacun a une raison.
Chaque entrée ports commence par 127.0.0.1. Le kernel n’accepte donc ces connexions que depuis le serveur lui-même. Toutes les connexions externes passent par le reverse proxy, qui est le seul composant à détenir un certificat.
Postgres ne contient aucun bloc ports. Le conteneur mem0 y accède via mem0_network en utilisant le nom du service. Publier 8432 ne vous apporte donc rien et vous expose à un port ouvert. Utilisez docker compose exec postgres psql -U postgres lorsque vous avez besoin d’un shell.
L’historique passe du bind mount ./history à un volume nommé. Un bind mount lie les données à un chemin et à un uid précis sur cet hôte, tandis qu’un volume nommé est un objet que Docker peut snapshotter et déplacer. Volumes nommés et bind mounts explique dans quels cas utiliser l’un ou l’autre.
La commande supprime --reload et conserve alembic upgrade head. Conservez cette étape de migration. Sans elle, l’application démarre avec une base de données dépourvue de tables et chaque requête échoue dès la première requête SQL.
NEXT_PUBLIC_API_URL est l’URL appelée par votre navigateur. Elle doit donc être l’adresse HTTPS publique, et non http://mem0:8000. Next.js intègre chaque valeur NEXT_PUBLIC_ au moment du build. Pour la modifier, vous devez donc utiliser docker compose up -d --build mem0-dashboard. Un simple redémarrage conserve l’ancienne valeur intégrée au JavaScript, et le dashboard appelle le mauvais hôte.
Les secrets résident dans .env, et .env ne doit pas être exposé sur Internet
cd server
cp .env.example .env
openssl rand -hex 32 # paste into JWT_SECRET
openssl rand -hex 32 # paste into ADMIN_API_KEY
chmod 600 .envDéfinissez POSTGRES_PASSWORD, JWT_SECRET et ADMIN_API_KEY. Laissez AUTH_DISABLED=false désactivé. Le nom décrit précisément le comportement de cette option : lorsqu’elle est activée, le serveur transmet toute la mémoire qu’il contient à quiconque peut atteindre le port. Définissez MEM0_TELEMETRY=false si vous ne voulez pas que l’événement d’intégration soit envoyé en amont.
ADMIN_API_KEY est comparé à l’en-tête X-API-Key avec secrets.compare_digest, et une correspondance ignore toutes les recherches dans la base de données. Il s’agit d’un identifiant root pour l’ensemble de l’API. Traitez-le comme tel : ni historique du shell, ni git, ni collage dans une invite de commande. Fichiers d’environnement Compose et fuites de secrets et garder les clés d’API hors du contexte d’un agent s’appliquent directement ici, car les clients de ce serveur sont des agents.
Les valeurs chargées depuis env_file se trouvent dans l’environnement du conteneur, et docker inspect les affiche intégralement. Toute personne appartenant au groupe docker peut les lire, et toute personne appartenant au groupe docker dispose de fait des privilèges root sur l’hôte.
Placez TLS devant l’API au lieu d’ouvrir le port 8888
L’API répond sur 127.0.0.1:8888 et le tableau de bord sur 127.0.0.1:3000. nginx termine TLS (sécurité de la couche transport) sur le port 443 et transmet les requêtes aux deux services.
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 est plus important qu’il n’y paraît. Un appel d’ajout reste bloqué pendant que le modèle de langage lit la conversation et en extrait les faits. Un modèle local de 8B exécuté sur le CPU dépasse régulièrement le délai par défaut de 60 secondes de nginx. L’appelant reçoit alors 504 Gateway Time-out alors que le modèle travaille encore et que la mémoire est toujours enregistrée. Vous vous retrouvez avec une mémoire qui vous a été signalée comme ayant échoué.
Fermez le reste avec une règle ufw par défaut qui refuse tout, en laissant les ports 22 et 443 ouverts. Émettez le certificat avec certbot sur Ubuntu 24.04 derrière nginx. Si le serveur sert déjà de reverse proxy à d’autres applications avec Traefik pour router plusieurs applications Compose, ajoutez mem0 à ce routeur au lieu d’installer un second proxy.
Test de bon fonctionnement : ajouter une mémoire et la relire
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"}'Une réponse correcte est un objet JSON contenant une liste results. Chaque entrée contient un id, le texte memory extrait et "event": "ADD". L’algorithme actuel ne renvoie que les événements ADD. Les événements UPDATE et DELETE ont été supprimés ; leur absence n’est donc pas un problème.
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}'Le fait concernant Postgres 17 doit être renvoyé avec un score. Transmettez l’identifiant dans filters, comme indiqué. Un user_id au niveau supérieur fonctionne toujours, et le serveur journalise Top-level user_id in /search is deprecated. Use filters={...} instead. à chaque utilisation.
Nettoyez après le test afin que les données de test ne polluent pas les recherches réelles :
curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
-H "X-API-Key: $MEM0_KEY"Si la recherche renvoie moins de lignes que prévu, vérifiez les valeurs par défaut avant de mettre en cause la récupération. Dans la version actuelle, top_k vaut 20 par défaut, contre 100 auparavant, et threshold vaut 0.1 au lieu de none : les correspondances faibles sont donc filtrées automatiquement. Une fois que cela fonctionne avec curl, ce sont ces mêmes endpoints que vous connecterez à un agent, directement ou via un serveur MCP exécuté sur le même VPS.
Exécuter mem0 sans aucune clé OpenAI
Commencez par le blocage, car vous le rencontrerez dans les cinq premières minutes. L’image du serveur inclut un ensemble fixe de bibliothèques de providers, et /configure refuse tout ce qui n’en fait pas partie :
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.Vous n’avez rien à recompiler. Ollama fournit une API compatible avec OpenAI à l’adresse /v1, qui couvre /v1/chat/completions et /v1/embeddings, et le provider OpenAI de mem0 accepte un openai_base_url. Pointez cette clé vers Ollama : le contrôle intégré réussit, car le provider est réellement openai. Seule l’adresse change.
Ajoutez Ollama au même projet Compose :
ollama:
image: ollama/ollama
restart: unless-stopped
networks: [mem0_network]
ports:
- "127.0.0.1:11434:11434"
volumes:
- ollama_models:/root/.ollamaAjoutez ollama_models: sous la clé de niveau supérieur volumes:, puis téléchargez un modèle de chat et un modèle d’embeddings :
docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-textSi Ollama s’exécute déjà sur l’hôte comme unité systemd, comme dans exécuter Ollama directement sur un VPS, ne pointez pas le conteneur vers 127.0.0.1:11434. Dans le conteneur mem0, 127.0.0.1 désigne le conteneur mem0. Donnez au service mem0 extra_hosts: ["host.docker.internal:host-gateway"], définissez Environment="OLLAMA_HOST=0.0.0.0:11434" dans un drop-in systemd afin qu’Ollama écoute sur une adresse accessible depuis le bridge, et laissez le port 11434 fermé au niveau du firewall.
Demandez au modèle la dimension de ses embeddings avant toute configuration
Cette étape détermine à elle seule si la recherche fonctionne.
Le store pgvector de mem0 crée sa table avec une largeur de vecteur fixe, vector vector(1536), car embedding_model_dims vaut par défaut 1536, la largeur de text-embedding-3-small d’OpenAI. nomic-embed-text renvoie 768 valeurs. Rien dans mem0 ne compare ces deux nombres. L’écart apparaît donc dans Postgres lors de la première insertion :
expected 1536 dimensions, not 768Ne faites pas non plus confiance au nombre indiqué dans ce paragraphe. Demandez-le au modèle :
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']))"Cette commande affiche la largeur à utiliser pour votre collection. Écrivez la configuration dans un fichier, car les erreurs de frappe en production viennent souvent d’un mot de passe Postgres passé dans les guillemets du 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"Le deuxième appel relit la configuration. C’est le contrôle qui confirme que l’écriture a bien abouti. Répétez ensuite le test de bon fonctionnement ci-dessus.
Quatre détails de ce JSON ne sont pas évidents. Chacun peut provoquer une erreur si vous le configurez mal.
api_key est la chaîne ollama, et Ollama ignore sa valeur. Elle ne peut pas être vide, car la bibliothèque cliente OpenAI lève une exception avant l’envoi de toute requête lorsqu’aucune clé n’est définie. N’importe quelle chaîne non vide convient.
embedding_model_dims se définit sur le vector store. Il n’y a volontairement pas de embedding_dims sur l’embedder. mem0 n’envoie le paramètre OpenAI dimensions que lorsque vous définissez embedding_dims, et les backends qui n’implémentent pas la troncature Matryoshka refusent directement ce paramètre. Définissez la largeur lors de la création de la table et laissez l’embedder inchangé.
collection_name est nouveau. mem0 crée sa table avec CREATE TABLE IF NOT EXISTS. Pointer une largeur différente vers une collection existante ne change donc absolument rien : l’ancienne colonne vector(1536) reste en place et chaque insertion échoue. Un changement de largeur nécessite un nouveau nom de collection, ou la suppression manuelle de l’ancienne table.
L’hôte dans openai_base_url est le nom du service Compose, ollama, et non localhost. Les conteneurs se résolvent entre eux à l’aide du nom du service sur leur réseau partagé.
Ce que vous coûte l’exécution entièrement locale
Soyez réaliste sur la qualité. Les scores publiés dans les benchmarks de mem0 ont été mesurés avec des modèles de pointe pour effectuer l’extraction. Considérez-les donc comme un plafond, et non comme une prévision pour un modèle 8B sur votre VPS. Un petit modèle produit des faits plus vagues. Il renvoie aussi parfois du texte alors qu’un JSON était demandé. Cela se traduit par un appel add qui renvoie une liste results vide, sans erreur.
La vitesse est l’autre coût. Une extraction réalisée uniquement sur le CPU prend plusieurs secondes par appel add, et chaque message que vous stockez en paie le prix. Un modèle qui continue à produire du texte après le JSON demandé aggrave le problème. Limiter la réponse avec num_predict plafonne donc la durée d’un appel add. Si cette latence est importante, un VPS avec un GPU attaché est la solution appropriée. Ajouter des cœurs CPU à un modèle 8B aide beaucoup moins que prévu. Changer de modèle coûte moins cher que changer de machine, et Nemotron 3.5 Lightning sur un VPS indique le tag à télécharger, la quantité de RAM nécessaire et si l’exécution uniquement sur le CPU est suffisamment rapide pour être acceptable.
Une règle reste valable dans tous les cas : ne mélangez jamais plusieurs modèles d’embeddings dans une même collection. Deux modèles différents qui utilisent par hasard la même largeur produisent des vecteurs incompatibles. L’insertion réussit, la recherche renvoie des lignes et ces lignes sont incorrectes. Rien ne signale l’erreur.
Sauvegardes : il y a deux bases de données, pas une seule
L’erreur la plus fréquente lors de la sauvegarde de mem0 consiste à exporter une seule base de données. init-db.sh crée mem0_app en parallèle de la base postgres par défaut, et ces bases contiennent des données différentes. La base postgres contient les collections pgvector, c’est-à-dire les souvenirs. mem0_app contient les utilisateurs, les sessions, les clés API et les journaux des requêtes. Chaque application auto-hébergée répartit son état à sa manière. C’est pourquoi deux serveurs photo qui remplissent la même fonction nécessitent malgré tout des commandes de sauvegarde différentes. Consultez donc les données stockées par votre application avant de faire confiance à un export. À l’autre extrémité, on trouve un cas comme une bibliothèque Jellyfin reconstruite comme un vidéoclub des années 90, qui lit tout son catalogue depuis un autre service et nécessite donc essentiellement la copie de sa configuration. mem0, en revanche, nécessite les deux bases de données ; sinon, la restauration est inutilisable.
Si vous restaurez uniquement postgres, les souvenirs réapparaissent, mais tous les comptes et toutes les clés d’API ont disparu. Rien ne peut donc s’authentifier pour les lire. Effectuez le dump des deux bases, ainsi que des rôles, avec une seule commande :
docker compose exec -T postgres pg_dumpall -U postgres --clean \
| gzip > "mem0-$(date +%F).sql.gz"Le volume d’historique est séparé de Postgres et nécessite sa propre copie :
docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
alpine tar czf /backup/mem0-history.tgz -C /data .Docker préfixe les noms des volumes avec le nom du projet. Vérifiez donc le vôtre avec docker volume ls avant de supposer qu’il s’agit de mem0_mem0_history.
Restaurez les données dans un conteneur temporaire et vérifiez le nombre de lignes avant de considérer la sauvegarde comme valide :
gunzip -c mem0-2026-08-03.sql.gz \
| docker compose exec -T postgres psql -U postgres -d postgresUne sauvegarde que vous n’avez jamais restaurée reste une simple supposition. Une fois les dumps vérifiés, envoyez-les hors du serveur avec des snapshots restic vers un stockage externe, car une sauvegarde qui se trouve sur le serveur qu’elle doit protéger ne protège rien.
Modes d’échec et chaînes exactes affichées
{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} signifie que l’en-tête est absent ou mal orthographié. Son nom est X-API-Key, et curl envoie les noms d’en-têtes tels quels.
{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} lors d’un ajout signifie que la requête n’en contenait aucun. Une mémoire doit être associée à un élément, car la recherche filtre exactement sur ces champs.
LLM provider 'ollama' is not bundled in this image avec HTTP 400 signifie que vous avez envoyé "provider": "ollama". Utilisez "provider": "openai" avec openai_base_url configuré pour pointer vers Ollama.
expected 1536 dimensions, not 768 dans Postgres signifie que la collection a été créée avec une largeur et que l’embedder en renvoie une autre. Définissez embedding_model_dims sur le vector store et utilisez un nouveau collection_name.
La recherche renvoie des lignes incohérentes après un changement de modèle, sans aucune erreur. La largeur correspond toujours, donc la base de données ne signale aucun problème, mais deux modèles placent la même phrase à des positions différentes. Créez une nouvelle collection et ajoutez de nouveau les données.
Connection refused dans les journaux mem0 lors de la connexion à Ollama signifie généralement que 127.0.0.1 dans openai_base_url. Dans le conteneur, cette adresse désigne le conteneur lui-même. Utilisez le nom du service ou la gateway de l’hôte lorsque Ollama s’exécute sur l’hôte.
504 Gateway Time-out depuis nginx lors d’un ajout signifie que le modèle a mis plus de temps que proxy_read_timeout. Augmentez cette valeur et vérifiez si la mémoire a malgré tout été écrite avant de relancer la requête.
exit code 137 pendant docker compose up --build signifie que l’out-of-memory killer a arrêté la construction du dashboard. Ajoutez du swap, ou construisez l’image sur une machine plus grande et envoyez-la vers un registry.
error: port 3000 is already in use provient de la cible make up du dépôt, qui refuse de démarrer lorsque 3000 ou 8888 sont déjà utilisés. Identifiez le processus propriétaire avec lsof -iTCP:3000 -sTCP:LISTEN.
FAQ
Ai-je encore besoin de Neo4j pour exécuter mem0 avec une mémoire graphe ?
Non. Le nouvel algorithme de mémoire, publié en avril 2026, a supprimé les clés de configuration graph_store et enable_graph du SDK open source. L’extraction des entités s’effectue maintenant lors d’un add normal et écrit dans une deuxième collection pgvector nommée <collection_name>_entities. Il n’y a donc plus de base de données graphe externe, de conteneur supplémentaire ni d’étape de migration. En contrepartie, le champ relations n’existe plus dans les résultats de recherche. Les entités augmentent maintenant le classement d’une mémoire au lieu de fournir des arêtes à parcourir. Une application qui parcourait ces relations doit donc utiliser son propre stockage graphe en dehors de mem0.
Quelle est la plus petite VPS capable d’exécuter un serveur mem0 auto-hébergé ?
Si le modèle de langage est hébergé ailleurs, 2 GB de RAM et environ 4 GB d’espace disque libre suffisent pour le conteneur API, Postgres et le dashboard. Le moment le plus exigeant est le premier build, car la compilation du dashboard Next.js utilise plus de mémoire que son exécution. Sur une machine de 1 GB, le build est interrompu avec exit code 137. Si Ollama s’exécute sur le même serveur, dimensionnez la machine en fonction du modèle : un modèle 8B avec une quantification sur 4 bits nécessite à lui seul environ 6 GB. Prévoyez donc 8 GB.
Puis-je exécuter mem0 sans clé API OpenAI ?
Oui, via l’endpoint compatible OpenAI d’Ollama. La définition de "provider": "ollama" échoue, car l’image du serveur contient uniquement les bibliothèques openai, anthropic et gemini et renvoie HTTP 400. Conservez plutôt "provider": "openai" et définissez "openai_base_url": "http://ollama:11434/v1" avec n’importe quel api_key non vide, à la fois pour le llm et pour l’embedder. Ollama ignore la clé, et le contrôle du provider intégré réussit puisque le provider est bien openai.
Pourquoi mem0 ne renvoie-t-il aucun résultat après le remplacement du modèle d’embeddings par un modèle local ?
Parce que la table pgvector a été créée avec une dimension fixe. embedding_model_dims utilise par défaut 1536 dimensions, nomic-embed-text en renvoie 768, et Postgres refuse l’insertion avec expected 1536 dimensions, not 768. mem0 crée la table avec CREATE TABLE IF NOT EXISTS. Modifier uniquement le nombre ne change donc rien dans une collection existante. Définissez embedding_model_dims sur la dimension réelle de votre modèle, vérifiez cette dimension en appelant /v1/embeddings et en comptant les valeurs renvoyées, puis attribuez en même temps un nouveau collection_name au vector store.