SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-28

mem0 su VPS: RAM, Docker Compose e Ollama locale

Scopri quanta RAM richiede davvero mem0 su un VPS: circa 1 GB senza modello locale, almeno 8 GB con Ollama 8B quantizzato a 4 bit, più Compose e TLS.

Quanto costa davvero in RAM eseguire mem0 in self-hosting su un VPS

Eseguire mem0 in self-hosting significa avviare tre container: il server FastAPI per la memoria, Postgres con l'estensione pgvector e una dashboard Next.js. mem0 è un livello di memoria per gli agenti. Si invia una conversazione al servizio, un modello linguistico estrae i fatti persistenti da quella conversazione e tali fatti vengono memorizzati come vettori, in modo che una query successiva possa recuperare quelli pertinenti.

Considerare circa 1 GB di memoria residente per i tre container e da 3 a 4 GB di spazio su disco dopo la creazione delle immagini. Un VPS da 2 GB esegue questa configurazione senza problemi quando il modello linguistico si trova altrove. Se il modello viene eseguito sullo stesso server tramite Ollama, il modello richiede molte più risorse di tutto il resto: un modello 8B quantizzato a 4 bit richiede da solo circa 6 GB, quindi una configurazione completamente locale richiede almeno 8 GB.

Non usare queste cifre come riferimento tratto da un post di un blog, incluso questo. Misura lo stack che hai effettivamente creato.

docker compose ps
docker stats --no-stream
docker system df -v

docker stats stampa la memoria residente per container. docker system df -v stampa lo spazio su disco occupato da ogni immagine e da ogni volume.

L'utilizzo a regime non corrisponde al picco. docker compose up -d --build compila la dashboard Next.js e questa compilazione Node rappresenta il momento con il maggiore consumo di memoria dell'intera installazione. Su un VPS da 1 GB, il kernel interrompe il processo tramite l'out-of-memory killer e la compilazione termina con exit code 137. Conferma la causa prima di cercare un bug di Docker:

dmesg -T | grep -i "killed process"

Se un server ti sembra un'infrastruttura eccessiva per le tue esigenze, esistono opzioni più semplici. un archivio locale della memoria degli agenti senza alcun server e la memoria gestita direttamente da Claude Code evitano entrambi il database. Torna a questa soluzione quando più agenti o più macchine devono leggere le stesse memorie.

È necessario Neo4j per la memoria a grafo di mem0?

No. Se una guida indica di aggiungere un container Neo4j, è più vecchia del codice attuale.

In passato, la memoria a grafo in mem0 indicava un database a grafo esterno, configurato con la chiave graph_store e con enable_graph impostato su true. Il nuovo algoritmo di memoria, rilasciato ad aprile 2026, ha rimosso entrambe le chiavi dall'SDK open source. L'estrazione delle entità viene ora eseguita nel normale percorso di aggiunta e le entità vengono scritte in una seconda raccolta pgvector, il cui nome è quello della raccolta principale con _entities aggiunto alla fine. Non è necessaria alcuna migrazione. Il collegamento integrato delle entità diventa operativo alla chiamata add successiva.

Rimuovere il datastore a grafo evita di eseguire un container JVM e di allocare il relativo heap, oltre a risparmiare diverse centinaia di megabyte per l'immagine. Su un VPS con 2 GB, può fare la differenza tra un sistema operativo e un sistema che usa lo swap.

Ecco cosa si perde, in modo diretto. In precedenza, i risultati della ricerca includevano un campo relations con l'elenco degli archi tra le entità. Quel campo non esiste più. Le corrispondenze tra entità ora aumentano la posizione di una memoria nel punteggio combinato e non esiste una struttura da attraversare. Se l'applicazione percorreva queste relazioni, mem0 non le conserva più. È quindi necessario mantenere un database a grafo separato da mem0 e alimentarlo con il proprio codice.

Il file Compose presente nel repository è un file Compose per lo sviluppo

server/docker-compose.yaml dichiara name: mem0-dev e lo fa effettivamente. Leggilo prima di eseguirlo, perché contiene cinque impostazioni errate per un server.

  • Esegue la build da server/dev.Dockerfile e monta il checkout sopra l’immagine tramite .:/app. Il container esegue quindi il contenuto presente in quella directory, non quello incluso nell’immagine creata.
  • Il comando è rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. A ogni avvio reinstalla mem0ai da PyPI. Di conseguenza, la versione eseguita dal server può cambiare durante un riavvio che non consideravi un aggiornamento.
  • Lo stesso passaggio con pip fa fallire il riavvio se non è disponibile una connessione in uscita, prima ancora che venga avviato uvicorn. Il server della memoria resta quindi inattivo perché PyPI non era raggiungibile.
  • --reload avvia il file watcher di uvicorn. Serve a riavviare il processo quando modifichi il codice. In produzione consuma memoria e avvia un secondo processo senza offrire alcun vantaggio. Il Dockerfile di produzione include inoltre --reload nel proprio CMD. Devi quindi sostituire il comando in entrambi i casi.
  • Le porte pubblicate sono "8888:8000", "8432:5432" e "3000:3000". Una porta pubblicata senza un indirizzo davanti viene associata a 0.0.0.0. Di conseguenza, Postgres risponde su Internet tramite la porta 8432 non appena lo stack viene avviato.

Quest’ultimo punto richiede un avviso specifico. Docker pubblica una porta inserendo le proprie regole prima della catena gestita da ufw. Per questo motivo, ufw deny 8432 non chiude una porta pubblicata dal container. La pubblicazione delle porte Docker passa direttamente oltre ufw descrive le regole coinvolte.

Un file Compose per un server reale

Lavora in server/, lascia init-db.sh nella posizione attuale e sostituisci docker-compose.yaml con questo contenuto.

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

Qui contano cinque modifiche, ognuna con una motivazione.

Ogni voce ports inizia con 127.0.0.1, quindi il kernel accetta queste connessioni soltanto dal server stesso. Tutto il traffico esterno passa attraverso il reverse proxy, che è l'unico componente a gestire un certificato.

Postgres non contiene alcun blocco ports. Il container mem0 lo raggiunge tramite mem0_network usando il nome del servizio, quindi pubblicare la porta 8432 non offre alcun vantaggio e lascia invece una porta aperta. Usa docker compose exec postgres psql -U postgres quando ti serve una shell.

La cronologia passa dal bind mount ./history a un volume denominato. Un bind mount lega i dati a un singolo percorso e a un singolo uid su questo host, mentre un volume denominato è un oggetto che Docker può acquisire come snapshot e spostare. Volumi denominati e bind mount spiega quando usare l'una o l'altra soluzione.

Il comando rimuove --reload e mantiene alembic upgrade head. Mantieni questo passaggio della migrazione. Senza di esso l'applicazione viene avviata su un database privo di tabelle e ogni richiesta fallisce alla prima query.

NEXT_PUBLIC_API_URL è l'URL che il browser chiama, quindi deve essere l'indirizzo HTTPS pubblico e non http://mem0:8000. Next.js inserisce ogni valore NEXT_PUBLIC_ nel bundle durante la compilazione, quindi per modificarlo è necessario docker compose up -d --build mem0-dashboard. Un semplice riavvio conserva il vecchio valore incorporato nel codice JavaScript e la dashboard chiama l'host errato.

I secret risiedono in .env, e .env non deve essere esposto a 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 .env

Imposta POSTGRES_PASSWORD, JWT_SECRET e ADMIN_API_KEY. Lascia AUTH_DISABLED=false invariato. Il nome descrive correttamente il comportamento di quel flag: quando è attivo, il server consegna tutta la memoria in suo possesso a chiunque riesca a raggiungere la porta. Imposta MEM0_TELEMETRY=false se non vuoi che l'evento di onboarding venga inviato a monte.

ADMIN_API_KEY viene confrontato con l'header X-API-Key usando secrets.compare_digest e, in caso di corrispondenza, tutte le ricerche nel database vengono saltate. È una credenziale root per l'intera API. Trattala come tale: niente cronologia della shell, niente git e niente incolla in un prompt. Si applicano direttamente anche File env di Compose e modalità di fuoriuscita dei secret e tenere le chiavi API fuori dal contesto di un agent, perché i client di questo server sono agent.

I valori caricati da env_file si trovano nell'ambiente del container e docker inspect li stampa integralmente. Chiunque appartenga al gruppo docker può leggerli, mentre chiunque appartenga al gruppo docker è di fatto root sull'host.

Metti TLS davanti all’API invece di esporre la porta 8888

L’API risponde su 127.0.0.1:8888 e la dashboard su 127.0.0.1:3000. nginx termina TLS (Transport Layer Security) sulla porta 443 e inoltra le richieste a entrambi.

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 è più importante di quanto sembri. Una chiamata add resta bloccata mentre il modello linguistico legge la conversazione ed estrae i fatti. Un modello locale 8B eseguito sulla CPU richiede regolarmente più tempo del valore predefinito di 60 secondi di nginx; il chiamante riceve quindi 504 Gateway Time-out mentre il modello è ancora in esecuzione e la memoria viene comunque scritta. Si ottiene così una memoria che il sistema ha segnalato come non riuscita.

Chiudi tutte le altre porte con una policy ufw predefinita con deny, lasciando aperte 22 e 443. Emetti il certificato con certbot su Ubuntu 24.04 dietro nginx. Se il server espone già altre applicazioni tramite Traefik per instradare più applicazioni Compose, aggiungi mem0 a quel router invece di installare un secondo proxy.

Test di base: aggiungere una memoria e rileggerla

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"}'

Una risposta corretta è un oggetto JSON con un elenco results. Ogni elemento contiene un id, il testo memory estratto e "event": "ADD". L'algoritmo attuale restituisce soltanto eventi ADD. Gli eventi UPDATE e DELETE sono stati rimossi, quindi la loro assenza non indica un problema.

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}'

Il dato su Postgres 17 dovrebbe essere restituito con un punteggio. Passa l'identificatore all'interno di filters, come mostrato. Un user_id al livello principale continua a funzionare e il server registra Top-level user_id in /search is deprecated. Use filters={...} instead. ogni volta che lo usi.

Pulisci l'ambiente al termine, in modo che i dati di test non influenzino le ricerche reali:

curl -sS -X DELETE "http://127.0.0.1:8888/memories?user_id=smoke" \
  -H "X-API-Key: $MEM0_KEY"

Se la ricerca restituisce meno righe del previsto, controlla i valori predefiniti prima di attribuire il problema al recupero. Nella versione attuale, top_k è impostato per impostazione predefinita su 20, invece di 100, mentre threshold è impostato su 0.1 anziché su nessun valore; di conseguenza, le corrispondenze deboli vengono ora filtrate automaticamente. Dopo aver verificato il funzionamento con curl, gli stessi endpoint sono quelli da collegare a un agente, direttamente oppure tramite un server MCP in esecuzione sullo stesso VPS.

Eseguire mem0 senza alcuna chiave OpenAI

Iniziate dal blocco, perché lo incontrerete nei primi cinque minuti. L’immagine del server include un insieme fisso di librerie dei provider e /configure rifiuta qualsiasi elemento al di fuori di quell’insieme:

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.

Non è necessario ricompilare nulla. Ollama espone un’API compatibile con OpenAI su /v1, che supporta /v1/chat/completions e /v1/embeddings, e il provider openai di mem0 accetta un openai_base_url. Puntate quella chiave a Ollama e il controllo incluso viene superato, perché il provider è effettivamente openai. Cambia soltanto l’indirizzo.

Aggiungete Ollama allo stesso progetto Compose:

  ollama:
    image: ollama/ollama
    restart: unless-stopped
    networks: [mem0_network]
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_models:/root/.ollama

Aggiungete ollama_models: sotto la chiave volumes: di primo livello, quindi scaricate un modello di chat e un modello di embedding:

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

Se Ollama è già in esecuzione sull’host come unità systemd, come descritto in eseguire Ollama direttamente su un VPS, non puntate il container a 127.0.0.1:11434. All’interno del container mem0, 127.0.0.1 è il container mem0. Assegnate al servizio mem0 extra_hosts: ["host.docker.internal:host-gateway"], impostate Environment="OLLAMA_HOST=0.0.0.0:11434" in un drop-in systemd affinché Ollama ascolti su un indirizzo raggiungibile dalla rete bridge e mantenete chiusa la porta 11434 nel firewall.

Chiedere al modello la dimensione degli embedding prima di configurare qualsiasi cosa

Questo passaggio determina se il recupero funziona.

Lo store pgvector di mem0 crea la tabella con una larghezza vettoriale fissa, vector vector(1536), perché embedding_model_dims per impostazione predefinita è 1536, la larghezza di text-embedding-3-small di OpenAI. nomic-embed-text restituisce 768 valori. Nulla all’interno di mem0 confronta questi due numeri, quindi la mancata corrispondenza viene segnalata da Postgres al primo inserimento:

expected 1536 dimensions, not 768

Non fidatevi nemmeno del numero riportato in questo paragrafo. Chiedetelo al modello:

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']))"

Questo comando stampa la larghezza che la collection deve utilizzare. Scrivete la configurazione in un file, perché inserire una password Postgres usando l’escaping della shell è un modo semplice per portare errori di battitura in produzione.

{
  "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"

La seconda chiamata legge nuovamente la configurazione ed è il controllo che conferma la riuscita della scrittura. Ripetete quindi lo smoke test precedente.

Nel JSON ci sono quattro dettagli non ovvi. Ognuno causa un malfunzionamento se viene configurato in modo errato.

api_key è la stringa ollama e Ollama ignora il relativo valore. Non può essere vuota, perché la libreria client OpenAI genera un errore prima che qualsiasi richiesta lasci il processo quando non è impostata alcuna chiave. È sufficiente una stringa non vuota qualsiasi.

embedding_model_dims va configurato sul vector store e non deve esserci alcun embedding_dims sull’embedder. mem0 invia il parametro OpenAI dimensions soltanto quando si imposta embedding_dims, e i backend che non implementano il troncamento Matryoshka rifiutano direttamente quel parametro. Impostate la larghezza al momento della creazione della tabella e lasciate invariato l’embedder.

collection_name è nuovo. mem0 crea la tabella con CREATE TABLE IF NOT EXISTS, quindi indicare una larghezza diversa per una collection esistente non produce alcun effetto: la vecchia colonna vector(1536) resta invariata e ogni inserimento fallisce. Per cambiare larghezza serve un nuovo nome di collection oppure è necessario eliminare manualmente la vecchia tabella.

L’host in openai_base_url è il nome del servizio Compose ollama, non localhost. I container si risolvono a vicenda tramite il nome del servizio sulla rete condivisa.

Il costo di un percorso completamente locale

Siate realistici sulla qualità. I punteggi dei benchmark pubblicati da mem0 sono stati misurati usando modelli frontier per l’estrazione. Considerateli quindi un limite superiore, non una previsione per un modello 8B sul vostro VPS. Un modello piccolo produce fatti più vaghi e talvolta restituisce testo discorsivo quando è stato richiesto JSON. Questo si manifesta con una chiamata add che restituisce un elenco results vuoto senza errori.

Anche la velocità ha un costo. L’estrazione eseguita soltanto sulla CPU richiede secondi per ogni chiamata add e ogni messaggio archiviato comporta questo costo. Un modello che continua a generare testo oltre il JSON richiesto peggiora il problema. Limitare la risposta con num_predict imposta quindi un limite alla durata di ogni singola chiamata add. Se questa latenza è un problema, un VPS con una GPU collegata è la soluzione corretta. Aggiungere altri core CPU a un modello 8B aiuta molto meno di quanto ci si aspetti. Cambiare modello è una leva meno costosa rispetto alla sostituzione della macchina e Nemotron 3.5 Lightning su un VPS indica il tag da scaricare, la RAM necessaria e se le prestazioni con la sola CPU sono sufficienti.

Vale una regola indipendentemente dalla scelta: non usate mai modelli di embedding diversi nella stessa collection. Due modelli diversi che per caso hanno la stessa larghezza producono vettori non confrontabili. L’inserimento riesce, la ricerca restituisce righe e le righe sono errate, senza che venga segnalato alcun errore.

Backup: ci sono due database, non uno

L’errore più comune nei backup di mem0 consiste nell’eseguire il dump di un solo database. init-db.sh crea mem0_app accanto al database predefinito postgres e i due database contengono dati diversi. Il database postgres contiene le raccolte pgvector, ovvero i ricordi. mem0_app contiene utenti, sessioni, chiavi API e log delle richieste. Ogni applicazione self-hosted suddivide il proprio stato in modo diverso. Per questo due server fotografici che svolgono lo stesso lavoro richiedono comunque comandi di backup diversi. Prima di affidarti a un dump, verifica quali dati archivia l’applicazione. All’estremo opposto si trova un caso come una libreria Jellyfin ricostruita come un videonoleggio degli anni ’90, che legge l’intero catalogo da un altro servizio e quindi richiede poco più della copia della propria configurazione. mem0, invece, richiede entrambi i database; in caso contrario, il ripristino è inutilizzabile.

Se ripristini soltanto postgres, le memorie tornano disponibili ma tutti gli account e le chiavi API scompaiono. Di conseguenza, nessun componente può autenticarsi per leggerle. Esegui il dump di entrambi i database, insieme ai ruoli, con un solo comando:

docker compose exec -T postgres pg_dumpall -U postgres --clean \
  | gzip > "mem0-$(date +%F).sql.gz"

Il volume della cronologia è separato da Postgres e richiede una propria copia:

docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
  alpine tar czf /backup/mem0-history.tgz -C /data .

Docker antepone ai nomi dei volumi il nome del progetto. Verifica il nome con docker volume ls prima di dare per scontato che sia mem0_mem0_history.

Ripristina il backup in un container temporaneo e controlla il numero di righe prima di considerarlo valido:

gunzip -c mem0-2026-08-03.sql.gz \
  | docker compose exec -T postgres psql -U postgres -d postgres

Un backup che non hai mai ripristinato è soltanto un’ipotesi. Quando i dump sono corretti, trasferiscili fuori dal server con snapshot restic su storage esterno, perché un backup che risiede sul server che dovrebbe proteggere non protegge nulla.

Modalità di errore e stringhe esatte visualizzate

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} indica che l'header è assente o scritto in modo errato. Il nome è X-API-Key e curl invia i nomi degli header alla lettera.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} durante un'operazione di aggiunta indica che la richiesta non ne conteneva nessuno. Una memoria deve essere associata a qualcosa, perché la ricerca filtra esattamente in base a quei campi.

LLM provider 'ollama' is not bundled in this image con HTTP 400 indica che è stato inviato "provider": "ollama". Usa "provider": "openai" con openai_base_url configurato per puntare a Ollama.

expected 1536 dimensions, not 768 da Postgres indica che la collection è stata creata con una dimensione e che l'embedder ne restituisce un'altra. Imposta embedding_model_dims nel vector store e usa un nuovo collection_name.

La ricerca restituisce righe prive di senso dopo una modifica del modello, senza errori nei log. La dimensione continua a corrispondere, quindi il database non rileva problemi, ma due modelli collocano la stessa frase in posizioni diverse. Crea una nuova collection e aggiungi nuovamente i dati.

Connection refused nei log di mem0 durante la connessione a Ollama indica solitamente 127.0.0.1 in openai_base_url. All'interno del container, quell'indirizzo identifica il container stesso. Usa il nome del servizio oppure il gateway dell'host se Ollama è in esecuzione sull'host.

504 Gateway Time-out da nginx durante un'operazione di aggiunta indica che il modello ha impiegato più tempo di proxy_read_timeout. Aumenta questo valore e verifica se la memoria è stata comunque scritta prima di ripetere la richiesta.

exit code 137 durante docker compose up --build indica che l'out-of-memory killer ha interrotto la build della dashboard. Aggiungi swap oppure crea l'immagine su una macchina più grande e inviala a un registry.

error: port 3000 is already in use proviene dal target make up del repository, che rifiuta di avviarsi quando le porte 3000 o 8888 sono già occupate. Trova il processo che le utilizza con lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Devo ancora usare Neo4j per eseguire mem0 con la memoria a grafo?

No. Il nuovo algoritmo di memoria, rilasciato ad aprile 2026, ha rimosso le chiavi di configurazione graph_store e enable_graph dall'SDK open source. L'estrazione delle entità viene ora eseguita durante una normale operazione di add e scrive in una seconda raccolta pgvector denominata <collection_name>_entities, quindi non sono necessari un database a grafo esterno, un container aggiuntivo o una procedura di migrazione. Il compromesso è che il campo relations non esiste più nei risultati di ricerca. Le entità ora aumentano il ranking di una memoria invece di fornire archi da attraversare. Un'applicazione che percorreva queste relazioni deve quindi usare un proprio archivio a grafo esterno a mem0.

Qual è il VPS più piccolo in grado di eseguire un server mem0 self-hosted?

Se il modello linguistico è ospitato altrove, 2 GB di RAM e circa 4 GB di spazio libero su disco sono sufficienti per il container API, Postgres e la dashboard. Il momento più impegnativo è la prima build, perché la compilazione della dashboard Next.js usa più memoria della sua esecuzione e su una macchina da 1 GB il processo di build viene terminato con exit code 137. Se Ollama viene eseguito sullo stesso server, dimensiona il VPS in base al modello: un modello 8B con quantizzazione a 4 bit richiede da solo circa 6 GB, quindi prevedi 8 GB.

Posso eseguire mem0 senza una chiave API OpenAI?

Sì, tramite l'endpoint compatibile con OpenAI di Ollama. L'impostazione di "provider": "ollama" non funziona perché l'immagine del server include solo le librerie openai, anthropic e gemini e restituisce HTTP 400. Mantieni invece "provider": "openai" e imposta "openai_base_url": "http://ollama:11434/v1" con un valore api_key non vuoto qualsiasi, sia per llm sia per embedder. Ollama ignora la chiave e il controllo del provider incluso va a buon fine perché il provider è effettivamente openai.

Perché mem0 non restituisce risultati dopo il passaggio a un modello di embedding locale?

Perché la tabella pgvector è stata creata con una dimensione fissa. embedding_model_dims ha come valore predefinito 1536, nomic-embed-text restituisce 768 e Postgres rifiuta l'inserimento con expected 1536 dimensions, not 768. mem0 crea la tabella usando CREATE TABLE IF NOT EXISTS, quindi modificare soltanto il numero non ha effetto su una raccolta esistente. Imposta embedding_model_dims sulla dimensione effettiva del modello, verifica tale dimensione chiamando /v1/embeddings e contando i valori restituiti, quindi assegna contemporaneamente al vector store un nuovo collection_name.