Every agent you build eventually hits the same wall: the model forgets. Session ends, context window overflows, or the user comes back tomorrow – and your carefully personalized assistant is a stranger again. An AI memory system is the layer that fixes this, and Mem0 is currently the most-deployed open-source option for it. Mem0 v1.0.0 shipped October 16, 2025 as a major release, and this guide walks through deploying the self-hosted stack – not the hosted API demo every other tutorial recycles.
Why self-host? Data stays on your infrastructure, you can swap in local LLMs, and the free tier’s 10K memory cap doesn’t apply. The trade-off: you’ll manage Postgres, a graph DB, and a few embedder gotchas the docs mention in passing.
System requirements
The self-hosted stack has three containers: the Mem0 API, Postgres with the pgvector extension, and Neo4j for graph memory. Per Mem0’s official Docker guide, the first run pulls roughly 500 MB of base images and takes 2-5 minutes to build.
| Component | Minimum | Recommended |
|---|---|---|
| OS | Linux / macOS / WSL2 | Ubuntu 22.04 or Amazon Linux 2023 |
| Python (SDK) | 3.10 | 3.12 (added in v0.1.116) |
| Node.js (TS SDK) | 18 | 20 LTS |
| RAM | 4 GB (t3.medium, ~$30/mo as of late 2025) | 8 GB (t3.large) – needed if running Ollama alongside |
| Docker | 24.x + Compose v2 | Latest stable |
Python 3.10 is the hard floor – the SDK will fail to import on 3.9. If you want just the Python client without the full server, pip install mem0ai is enough. The rest of this guide assumes you want the real deployment.
Where to get the files
The canonical source is the mem0ai/mem0 monorepo. The self-host Docker files live in openmemory/ and server/ – which one to use is a common confusion. Use server/ for the REST API. Use openmemory/ for the MCP-style local memory service (UI expected but not shipped – more on that below).
git clone https://github.com/mem0ai/mem0.git
cd mem0/server
Install with Docker
Minimum viable stack. Create a .env file in the server/ directory:
OPENAI_API_KEY=sk-your-key-here
POSTGRES_USER=mem0
POSTGRES_PASSWORD=changeme
POSTGRES_DB=mem0
NEO4J_AUTH=neo4j/changeme
Then bring the stack up:
docker compose up -d --build
--build is only needed on the first run or after Dockerfile changes. Watch the logs with docker compose logs -f mem0. When you see Uvicorn running on http://0.0.0.0:8000, the API is live.
Watch out: If you hit
ModuleNotFoundError: No module named 'psycopg_pool'orpsycopg2, you’ve run into issue #3753 – the shipped Dockerfile omits Postgres drivers when pgvector is the configured backend. Addpsycopg[binary,pool]andpsycopg2-binarytorequirements.txtand rebuild. Still open as of this writing.
The dimension-mismatch trap
Here’s the thing that quietly kills most local installs: swap your embedder after the first write, and every subsequent insert crashes. The pgvector table gets a fixed column dimension the moment Mem0 first writes to it – determined by whichever embedder you had configured. OpenAI’s text-embedding-3-small: 1536 dimensions. Ollama’s nomic-embed-text: 768. Change embedders with data already in the table and you get DataException: expected 1536 dimensions, not 768 on every single insert, forever, until you wipe the volumes.
Declare the dimension explicitly in your config before the first write:
from mem0 import Memory
config = {
"vector_store": {
"provider": "pgvector",
"config": {
"host": "localhost",
"port": 5432,
"embedding_model_dims": 768
}
},
"embedder": {
"provider": "ollama",
"config": {"model": "nomic-embed-text"}
}
}
m = Memory.from_config(config)
Wrong dimension already baked in? No in-place migration exists. docker compose down -v wipes the volumes – then rebuild and re-add your data.
Verify it works
Two checks. First, container status:
docker compose ps
# Three services: mem0, postgres, neo4j - all Up
curl -s http://localhost:8000/docs
Second, a round trip. Skip the vegetarian demo – try something from your actual domain:
from mem0 import Memory
m = Memory()
m.add([
{"role": "user", "content": "Deploy production to eu-west-1, staging to us-east-2"}
], user_id="devops-42")
print(m.search("where does prod live?", user_id="devops-42"))
Similarity score above ~0.4 means the extraction pipeline, embedder, and vector store are all wired correctly.
Think of what just happened: you wrote a sentence in natural language, Mem0 extracted a structured fact from it, embedded that fact as a vector, stored it, then retrieved it in response to a semantically different query. That’s the whole pipeline in four lines. Whether that extraction step got the fact right – and whether it discarded the unimportant parts – is the question the Mem0 research paper (arXiv:2504.19413) benchmarks. Worth skimming before you trust the system with anything sensitive.
Common install errors – pulled from live GitHub issues
- Data disappears after
docker-compose down– the OpenMemoryrun.shinstaller generates a compose file with the Qdrant volume mounted at/mem0/storage, but Qdrant actually writes to/qdrant/storage(GitHub issue #2895). Edit the compose file, fix the mount path, restart. This is not a user error. LLM_PROVIDERorOLLAMA_BASE_URLenv vars ignored – OpenMemory persists config in a SQLite file calledopenmemory.db, not the environment. Issue #4194 confirms.envonly gates the initialOPENAI_API_KEYstartup check. Change config via the API or deleteopenmemory.dband re-init.- “Where’s the dashboard?” – there isn’t one for self-hosted, as of this writing. GitHub discussion #3599 has been open for months with no shipped UI. Inspect memories through the vector store’s own interface (Qdrant has one at
:6333/dashboard) or query Postgres directly.
Upgrading and cleanup
SDK upgrade: pip install --upgrade mem0ai, done. The self-hosted stack across the v1.0.0 boundary is different – the API was modernized and has breaking changes from 0.1.x. Pin your version in production. Check the official changelog before every bump.
Full cleanup:
docker compose down -v # stops containers, removes named volumes
docker rmi $(docker images 'mem0*' -q)
pip uninstall mem0ai
rm -rf ~/mem0 openmemory.db
The -v flag is the one that actually deletes stored memories. Without it, Postgres and Neo4j volumes persist – the next up restores everything. Useful sometimes, catastrophic other times.
A note on trust and inference
An AI memory system looks like a database but behaves more like a coworker who takes selective notes – and every extraction call costs LLM tokens whether the fact was worth remembering or not. The part that doesn’t get enough attention: memories here are inferred, not written. How do you audit that? The arXiv paper benchmarks accuracy at retrieval, but the extraction step is where the real trust question lives. Something to keep in mind before you wire this into anything production-critical.
FAQ
Can I run Mem0 fully offline with local models?
Yes – Ollama for both the LLM and embedder, Qdrant or pgvector locally. The one thing to get right upfront: set embedding_model_dims: 768 before the first insert. See the dimension-mismatch section above for why that matters.
Do I need Neo4j if I only want vector memory?
No. Drop the Neo4j service from your compose file and save roughly 1 GB of RAM. Neo4j only comes into play for graph memory – entity relationships, multi-hop queries across connected facts. If your use case is simpler (user preferences, past interactions, factual recall), pgvector alone handles it. Most early-stage projects don’t need graph memory at all; it’s easier to add it later than to run an unnecessary service from day one.
How does self-hosted compare to the paid platform for a real project?
The hosted platform’s free tier (as of late 2025) gives you 10,000 add requests and 1,000 retrieval requests per month. Starter is $19/mo (50K adds / 5K retrievals). Pro at $249/mo adds graph memory, unlimited memories, and analytics. Self-hosted is Apache 2.0 – no caps, you pay only for the VM and LLM tokens. One thing the pricing page doesn’t mention clearly: the managed platform includes proprietary optimizations not in the OSS SDK, per the project README. Accuracy will be directionally similar but not identical. If you’re evaluating which path to take, run both against your actual workload for a week rather than trusting benchmark numbers alone.