How I Self-Hosted a Mem0 Memory Stack (So My AI Agent Remembers)

· 5 min read · case-studies mem0dockerpostgrespgvectorlitellmaisharing

An AI agent that forgets everything between conversations is a tool you have to reintroduce yourself to every session. My AI assistant (the one that runs this very blog’s workflow) used to start each chat from zero — until I gave it a memory layer: a self-hosted mem0 stack that stores facts it learns, retrieves them semantically, and survives across sessions.

This is the story of how I self-hosted it on my Synology NAS — private, fully under my control, no data leaving my LAN.

Why this matters

A memory layer changes what an agent can actually do for a business:

For anyone running AI agents (support bots, research assistants, personal automation) the question isn’t should the agent remember — it’s whether the memory is a private asset or a third party’s.

What mem0 does

Mem0 is “memory in a box” for LLM apps. Instead of you hand-rolling an embeddings table and a similarity search, it gives you a small API that does the three things memory needs:

  1. Add a memoryPOST /memories with a fact or a conversation; it extracts, dedupes, and stores it (optionally classified by an LLM).
  2. SearchGET /search with a query returns the most relevant stored facts, ranked by semantic similarity.
  3. User scoping — memories belong to a user_id, so you can keep memory per customer, per project, or per conversation without cross-talk.

The “memory” is the extracted fact. The “retrieval” is vector similarity. You get the agent even if you never touch a vector database yourself.

The stack

Three containers on my Synology DS1821+, as a Portainer compose stack:

┌──────────────┐      /search & /memories       ┌──────────────────┐
│ AI agent /   │ ──── X-Api-Key + JSON ───────▶ │  mem0 API :20015 │
│ Hermes / bot │                                └────────┬─────────┘
└──────────────┘                                         │ pgvector

                                             PostgreSQL 17 + pgvector
┌──────────────┐     model calls (extract)    ┌──────────────────┐
│ OpenAI etc.  │ ◀───────── LiteLLM ──────────│ fact-classifier  │
└──────────────┘                              └──────────────────┘

A representative compose looks like this:

services:
  mem0-postgres:
    image: pgvector/pgvector:pg17
    environment:
      POSTGRES_USER: mem0
      POSTGRES_PASSWORD: ${MEM0_DB_PASSWORD}
      POSTGRES_DB: mem0
    volumes:
      - ./data/pg:/var/lib/postgresql/data
    restart: unless-stopped

  mem0-litellm:
    image: ghcr.io/berriai/litellm:main-stable
    command: ["--config", "/app/config.yaml"]
    volumes:
      - ./litellm-config.yaml:/app/config.yaml
    restart: unless-stopped

  mem0-api:
    build: ./mem0-api
    environment:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      OPENAI_API_BASE: http://mem0-litellm:4000/v1   # go through the gateway
      MEM0_API_KEY: ${MEM0_API_KEY}                  # X-Api-Key for /search & /memories
      POSTGRES_URL: postgresql://mem0:${MEM0_DB_PASSWORD}@mem0-postgres/mem0
    ports:
      - "20015:8000"
    restart: unless-stopped

(Mine is built from the mem0 repo with the model/fact-extraction directed through LiteLLM; the exact image tag is me maintaining a local build, and I keep the real API key out of the compose file — it’s an env secret.)

Using it

Once it’s up, the API is refreshingly small. Add a fact (fast, verbatim):

curl -X POST http://192.168.1.1:20015/memories \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $MEM0_API_KEY" \
  -d '{
    "user_id": "customer-2211",
    "text": "Customer 2211 prefers email over WhatsApp for order updates."
  }'

Retrieve the most relevant memories later:

curl -X POST http://192.168.1.1:20015/memories/search \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: $MEM0_API_KEY" \
  -d '{
    "user_id": "customer-2211",
    "query": "how should I reach this customer?",
    "limit": 5
  }'

Then inject the returned facts into the agent’s system prompt (or context) before it answers. That’s the whole loop: store facts, retrieve what’s relevant, use it in the next reply.

The infer gotcha that cost me a day

The route of my stack is /search and /memories without a /v1 prefix (an early wrapper that assumed /v1 404’d for a while), and auth is a plain X-Api-Key header. Small details, but they cost me time to discover.

What I’d do differently

The result

A private, self-hosted memory layer answered by two small HTTP calls. My agent now remembers customers, preferences, and decisions across sessions — and because the stack sits in my own PostgreSQL behind my own network, none of that leaves my control.

If you run AI agents that keep forgetting their context, self-hosting mem0 is one of the highest-value, lowest-friction upgrades you can make — and you can do it without sending your memory to someone else’s database.


Want a memory-enabled AI setup for your business?

I build and self-host AI agents, memory stacks, websites, and infrastructure for businesses. If you’d like your assistant to actually remember your customers — or you want to talk about hiring me — I’d love to help:

Lee Teong Hoe

Full-stack developer & DevOps engineer. I build web apps, self-host infrastructure, and automate things — this blog is my living portfolio.