SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Jak samodzielnie hostować mem0 na VPS: poradnik

Dowiedz się jak uruchomić mem0 na własnym VPS. Artykuł zawiera gotowy plik Compose, konfigurację TLS dla API, wymogi RAM dla kontenerów oraz integrację z lokalnym modelem Ollama.

Rzeczywiste koszty pamięci RAM przy self-hostingu mem0 na VPS

Self-hosting mem0 oznacza uruchomienie trzech kontenerów: serwera pamięci FastAPI, bazy danych Postgres z rozszerzeniem pgvector oraz panelu sterowania Next.js. mem0 stanowi warstwę pamięci dla agentów. Przesyła się do niej konwersację, model językowy wyodrębnia z niej trwałe fakty, a te są zapisywane jako wektory, aby późniejsze zapytanie mogło pobrać odpowiednie informacje.

Należy zarezerwować około 1 GB pamięci rezydentnej dla trzech kontenerów oraz od 3 do 4 GB miejsca na dysku po zbudowaniu obrazów. Serwer VPS z 2 GB RAM obsłuży to bez problemów, o ile model językowy działa na innej maszynie. Gdy model uruchamiany jest na tym samym serwerze przez Ollama, dominuje on nad wszystkimi innymi procesami: model 8B skwantyzowany do 4 bitów wymaga około 6 GB pamięci, więc w pełni lokalna instalacja wymaga 8 GB RAM.

Nie należy polegać wyłącznie na tych liczbach z wpisów na blogach, w tym niniejszego. Należy zmierzyć stos technologiczny, który faktycznie został wdrożony.

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

docker stats wyświetla pamięć rezydentną dla każdego kontenera. docker system df -v wyświetla zajętość dysku przez każdy obraz i wolumen.

Stan ustalony nie jest stanem szczytowym. docker compose up -d --build kompiluje panel Next.js, a proces budowania Node jest najbardziej zasobożernym momentem całej instalacji. Na serwerze VPS z 1 GB RAM mechanizm OOM (out-of-memory) killera jądra systemu przerywa ten proces, a budowanie kończy się błędem exit code 137. Przed szukaniem błędu w Docker należy potwierdzić przyczynę:

dmesg -T | grep -i "killed process"

Jeśli serwer wydaje się zbyt rozbudowanym rozwiązaniem dla bieżących potrzeb, istnieją mniejsze alternatywy. lokalny magazyn pamięci agenta bez serwera oraz pamięć działająca wewnątrz Claude Code pozwalają pominąć bazę danych. Warto wrócić do tego rozwiązania, gdy kilku agentów lub kilka maszyn musi korzystać z tych samych zasobów pamięci.

Czy do obsługi pamięci grafowej mem0 wymagany jest Neo4j?

Nie. Jeśli poradnik zaleca dodanie kontenera Neo4j, jest on nieaktualny względem obecnego kodu.

Pamięć grafowa w mem0 oznaczała wcześniej zewnętrzną bazę danych grafowych, konfigurowaną za pomocą klucza graph_store z ustawieniem enable_graph na true. Nowy algorytm pamięci, wprowadzony w kwietniu 2026, usunął oba te klucze z SDK open source. Ekstrakcja encji odbywa się teraz w ramach standardowej ścieżki add, a encje są zapisywane w drugiej kolekcji pgvector, której nazwa pochodzi od głównej kolekcji z dodanym przyrostkiem _entities. Nie jest wymagana żadna migracja. Wbudowane łączenie encji zaczyna działać przy kolejnym wywołaniu add.

Rezygnacja z magazynu grafowego pozwala zaoszczędzić kontener JVM, jego stertę (heap) oraz kilkaset megabajtów obrazu. Na serwerze VPS o pojemności 2 GB stanowi to różnicę między poprawnym działaniem a korzystaniem ze swapu.

Oto co tracisz, przedstawione wprost. Wyniki wyszukiwania zawierały wcześniej pole relations, wymieniające krawędzie między encjami. To pole zostało usunięte. Dopasowania encji podnoszą teraz pozycję pamięci w łącznym wyniku punktowym, ale brak jest struktury, którą można przeszukiwać. Jeśli aplikacja korzystała z tych relacji, mem0 już ich nie przechowuje; w takim przypadku należy utrzymywać własną bazę danych grafowych poza mem0, zasilaną własnym kodem.

Plik compose w repozytorium jest wersją deweloperską

server/docker-compose.yaml deklaruje name: mem0-dev i należy traktować to dosłownie. Przed uruchomieniem pliku należy się z nim zapoznać, ponieważ zawiera on pięć elementów nieodpowiednich dla serwera produkcyjnego.

  • Buduje obraz z server/dev.Dockerfile i montuje katalog roboczy nad obrazem za pomocą .:/app, przez co kontener uruchamia pliki znajdujące się w tym katalogu, a nie te, które zostały wbudowane w obraz.
  • Polecenie startowe to rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Powoduje ono reinstalację mem0ai z PyPI przy każdym starcie, więc wersja działająca na serwerze może ulec zmianie podczas restartu, który nie był planowaną aktualizacją.
  • Ten sam krok z użyciem pip sprawia, że restart przy braku połączenia z Internetem kończy się niepowodzeniem, zanim uvicorn zdąży wystartować. Serwer pamięci przestaje działać, ponieważ usługa PyPI była nieosiągalna.
  • --reload uruchamia mechanizm śledzenia plików uvicorn. Służy on do restartowania procesu podczas edycji kodu, co w środowisku produkcyjnym niepotrzebnie zużywa pamięć i tworzy dodatkowy proces. Produkcyjny Dockerfile również zawiera --reload w swoim CMD, więc w obu przypadkach polecenie jest nadpisywane.
  • Opublikowane porty to "8888:8000", "8432:5432" oraz "3000:3000". Opublikowanie portu bez wskazania adresu wiąże go z 0.0.0.0, co oznacza, że Postgres odpowiada na zapytania z publicznego Internetu na porcie 8432 natychmiast po uruchomieniu stosu.

Ostatni punkt wymaga szczególnej uwagi. Docker publikuje porty, dodając własne reguły przed łańcuchem zarządzanym przez ufw, dlatego ufw deny 8432 nie blokuje portu kontenera wystawionego w ten sposób. Artykuł Publikowanie portów przez Docker z pominięciem ufw szczegółowo omawia działanie tych reguł.

Plik compose dla serwera produkcyjnego

Pracuj wewnątrz server/, zachowaj init-db.sh w obecnym miejscu i zastąp docker-compose.yaml poniższą treścią.

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

W tym przypadku istotnych jest pięć zmian, z których każda ma swoje uzasadnienie.

Każdy wpis ports rozpoczyna się od 127.0.0.1, dzięki czemu jądro akceptuje te połączenia wyłącznie z poziomu samego hosta. Cały ruch z zewnątrz dociera przez reverse proxy, który jako jedyny posiada certyfikat.

Postgres nie posiada sekcji ports. Kontener mem0 uzyskuje do niego dostęp poprzez mem0_network, używając nazwy usługi, więc publikowanie portu 8432 nie przynosi korzyści, a jedynie otwiera zbędny port. Użyj docker compose exec postgres psql -U postgres, gdy potrzebujesz powłoki.

Historia została przeniesiona z bind mount ./history do wolumenu nazwanego. Bind mount wiąże dane z konkretną ścieżką i identyfikatorem UID na hoście, podczas gdy wolumen nazwany jest obiektem, który Docker może migawkować i przenosić. Wolumeny nazwane a bind mounts wyjaśnia, kiedy stosować poszczególne rozwiązania.

Polecenie usuwa --reload i zachowuje alembic upgrade head. Ten krok migracji jest niezbędny. Bez niego aplikacja uruchamia się z bazą danych pozbawioną tabel, co powoduje, że każde żądanie kończy się błędem przy pierwszym zapytaniu.

NEXT_PUBLIC_API_URL to adres URL wywoływany przez przeglądarkę, dlatego musi to być publiczny adres HTTPS, a nie http://mem0:8000. Next.js wstawia każdą wartość NEXT_PUBLIC_ na etapie budowania, więc jej zmiana wymaga docker compose up -d --build mem0-dashboard. Zwykły restart zachowuje starą wartość zapisaną w kodzie JavaScript, przez co panel sterowania odwołuje się do niewłaściwego hosta.

Sekrety przechowuje się w .env, a plik .env nie powinien być dostępny z Internetu

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

Skonfiguruj POSTGRES_PASSWORD, JWT_SECRET oraz ADMIN_API_KEY. Pozostaw AUTH_DISABLED=false bez zmian. Nazwa tego flagi precyzyjnie określa jej działanie: po włączeniu serwer udostępnia całą zawartość pamięci każdemu, kto uzyska dostęp do portu. Ustaw MEM0_TELEMETRY=false, jeśli zdarzenie onboardingu nie powinno być wysyłane do systemu nadrzędnego.

Wartość ADMIN_API_KEY jest porównywana z nagłówkiem X-API-Key za pomocą secrets.compare_digest, a dopasowanie pomija każde zapytanie do bazy danych. Jest to poświadczenie typu root dla całego API. Należy traktować je odpowiednio: nie zapisywać w historii powłoki, nie dodawać do git, nie wklejać do terminala. Artykuły Pliki Compose env i miejsca wycieku sekretów oraz utrzymywanie kluczy API poza kontekstem agenta mają tutaj bezpośrednie zastosowanie, ponieważ klientami tego serwera są agenty.

Wartości wczytane z env_file znajdują się w środowisku kontenera, a docker inspect wyświetla je w pełnej postaci. Każdy użytkownik w grupie docker może je odczytać, a każdy użytkownik w grupie docker posiada w praktyce uprawnienia root na hoście.

Umieszczenie TLS przed API zamiast otwierania portu 8888

API odpowiada na 127.0.0.1:8888, a panel sterowania na 127.0.0.1:3000. Nginx dokonuje terminacji TLS (transport layer security) na porcie 443 i przekazuje ruch do obu usług.

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 ma większe znaczenie, niż się wydaje. Wywołanie dodawania blokuje operację, podczas gdy model językowy analizuje konwersację i wyodrębnia fakty. Lokalny model 8B działający na CPU regularnie potrzebuje więcej czasu niż domyślny limit 60 sekund w Nginx, przez co wywołujący otrzymuje 504 Gateway Time-out, podczas gdy model nadal pracuje, a pamięć jest zapisywana. W efekcie powstaje wpis w pamięci, mimo otrzymania komunikatu o błędzie.

Zablokuj pozostałe porty za pomocą domyślnej polityki deny w ufw, pozostawiając otwarte jedynie 22 i 443. Wystaw certyfikat przy użyciu certbot na Ubuntu 24.04 za Nginx. Jeśli serwer obsługuje już inne aplikacje przez Traefik routujący kilka aplikacji Compose, dodaj mem0 do tego routera zamiast instalować drugie proxy.

Test dymny: dodaj jeden wpis do pamięci i odczytaj go

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

Prawidłowa odpowiedź to obiekt JSON z listą results, gdzie każdy wpis zawiera id, wyekstrahowany tekst memory oraz "event": "ADD". Obecny algorytm zwraca wyłącznie zdarzenia typu ADD. Zdarzenia UPDATE oraz DELETE zostały usunięte, więc ich brak nie jest błędem.

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

Fakt dotyczący Postgres 17 powinien zostać zwrócony wraz z wynikiem punktowym. Przekaż identyfikator wewnątrz filters, zgodnie z przykładem. Wartość user_id na najwyższym poziomie nadal działa, a serwer rejestruje Top-level user_id in /search is deprecated. Use filters={...} instead. przy każdym jej użyciu.

Posprzątaj po zakończeniu testu, aby dane testowe nie zanieczyszczały rzeczywistych wyników wyszukiwania:

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

Jeśli wyszukiwanie zwraca mniej wierszy niż oczekiwano, sprawdź wartości domyślne przed weryfikacją mechanizmu pobierania danych. W bieżącym wydaniu top_k domyślnie wynosi 20 (wcześniej 100), a threshold domyślnie wynosi 0.1 zamiast braku ograniczenia, więc słabe dopasowania są teraz automatycznie odfiltrowywane. Gdy test zadziała przez curl, te same punkty końcowe należy podłączyć do agenta, bezpośrednio lub przez serwer MCP działający na tym samym VPS.

Uruchomienie mem0 bez klucza OpenAI

Zacznij od blokady, ponieważ napotkasz ją w ciągu pierwszych pięciu minut. Obraz serwera zawiera ustalony zestaw bibliotek dostawców, a /configure odrzuca wszystko, co wykracza poza ten zakres:

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.

Nie musisz niczego przebudowywać. Ollama udostępnia API zgodne z OpenAI pod adresem /v1, obsługując /v1/chat/completions oraz /v1/embeddings, a dostawca openai w mem0 akceptuje openai_base_url. Skieruj ten klucz na Ollama, a wbudowana weryfikacja przejdzie pomyślnie, ponieważ dostawca faktycznie jest openai. Zmienia się tylko adres.

Dodaj Ollama do tego samego projektu Compose:

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

Dodaj ollama_models: w sekcji głównej volumes:, a następnie pobierz jeden model czatu i jeden model osadzeń (embedding):

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

Jeśli Ollama działa już na hoście jako jednostka systemd, zgodnie z uruchamianiem Ollama bezpośrednio na VPS, nie kieruj kontenera na 127.0.0.1:11434. Wewnątrz kontenera mem0, 127.0.0.1 to sam kontener mem0. Nadaj usłudze mem0 extra_hosts: ["host.docker.internal:host-gateway"], ustaw Environment="OLLAMA_HOST=0.0.0.0:11434" w pliku typu drop-in systemd, aby Ollama nasłuchiwała na adresie dostępnym z poziomu mostka sieciowego, i zablokuj port 11434 na firewallu.

Zapytaj model o wymiar osadzeń przed konfiguracją czegokolwiek

Ten jeden krok decyduje o tym, czy wyszukiwanie w ogóle zadziała.

Magazyn pgvector w mem0 tworzy tabelę o stałej szerokości wektora, vector vector(1536), ponieważ embedding_model_dims domyślnie przyjmuje wartość 1536, czyli szerokość modelu text-embedding-3-small od OpenAI. nomic-embed-text zwraca 768 wartości. Nic wewnątrz mem0 nie porównuje tych dwóch liczb, więc rozbieżność ujawnia się po stronie Postgres przy pierwszej operacji insert:

expected 1536 dimensions, not 768

Nie ufaj również liczbie podanej w tym akapicie. Zapytaj model:

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

To polecenie wyświetli szerokość, której musi używać Twoja kolekcja. Zapisz konfigurację do pliku, ponieważ wklejanie hasła do Postgres przez cytowanie w powłoce to najprostsza droga do wprowadzenia literówek na produkcję.

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

Drugie wywołanie odczytuje konfigurację, co stanowi weryfikację poprawności zapisu. Następnie powtórz powyższy test sprawnościowy.

Cztery szczegóły w tym formacie JSON nie są oczywiste, a każdy z nich powoduje błąd w przypadku pomyłki.

api_key to ciąg znaków ollama, a Ollama ignoruje jego wartość. Nie może być on pusty, ponieważ biblioteka klienta OpenAI zgłasza błąd przed wysłaniem żądania, jeśli nie ustawiono żadnego klucza. Każdy niepusty ciąg znaków zadziała.

embedding_model_dims dotyczy magazynu wektorów, a w konfiguracji embeddera celowo nie ma embedding_dims. mem0 wysyła parametr OpenAI dimensions tylko wtedy, gdy ustawisz embedding_dims, a backendy, które nie implementują obcinania Matryoshka, odrzucają ten parametr. Ustaw szerokość w miejscu tworzenia tabeli i nie zmieniaj ustawień embeddera.

collection_name jest nowością. mem0 tworzy tabelę z CREATE TABLE IF NOT EXISTS, więc wskazanie innej szerokości dla istniejącej kolekcji nie przyniesie żadnego efektu: stara kolumna vector(1536) pozostanie, a każda operacja insert zakończy się niepowodzeniem. Zmiana szerokości wymaga nowej nazwy kolekcji lub ręcznego usunięcia starej tabeli.

Host w openai_base_url to nazwa usługi Compose ollama, a nie localhost. Kontenery rozpoznają się nawzajem po nazwie usługi w ramach współdzielonej sieci.

Koszty w pełni lokalnego rozwiązania

Bądź szczery wobec siebie w kwestii jakości. Opublikowane wyniki testów wydajności mem0 zostały zmierzone przy użyciu modeli klasy frontier do ekstrakcji danych, więc traktuj je jako górny limit, a nie prognozę dla modelu 8B na Twoim VPS. Mniejszy model zapisuje bardziej ogólne fakty i czasami zwraca tekst zamiast żądanego formatu JSON, co objawia się wywołaniem add zwracającym pustą listę results bez błędu.

Drugim kosztem jest szybkość. Ekstrakcja wykonywana wyłącznie na procesorze (CPU) zajmuje sekundy przy każdym wywołaniu add, a każda zapisywana wiadomość zwiększa ten czas. Model, który wykracza poza żądany format JSON, pogarsza sytuację, dlatego ograniczenie odpowiedzi za pomocą num_predict ustala limit czasu trwania pojedynczego wywołania add. Jeśli opóźnienie ma znaczenie, VPS z dołączonym GPU jest uczciwym rozwiązaniem. Dodawanie większej liczby rdzeni CPU do modelu 8B pomaga znacznie mniej, niż się oczekuje. Zmiana modelu jest tańszym narzędziem niż zmiana maszyny, a Nemotron 3.5 Lightning na VPS wskazuje tag do pobrania, wymagania pamięci RAM oraz to, czy szybkość działania wyłącznie na CPU jest akceptowalna.

Obowiązuje jedna zasada, niezależnie od wyboru: nigdy nie mieszaj modeli osadzeń w jednej kolekcji. Dwa różne modele, które przypadkowo mają tę samą szerokość, generują wektory, których nie można ze sobą porównywać. Operacja insert zakończy się sukcesem, wyszukiwanie zwróci wiersze, ale będą one błędne, a żaden system nie zgłosi błędu.

Kopie zapasowe: istnieją dwie bazy danych, nie jedna

Najczęstszy błąd podczas tworzenia kopii zapasowej mem0 polega na zrzuceniu tylko jednej bazy danych. init-db.sh tworzy bazę mem0_app obok domyślnej bazy postgres, a obie przechowują różne dane. Baza postgres przechowuje kolekcje pgvector, czyli wspomnienia. mem0_app przechowuje użytkowników, sesje, klucze API i dzienniki żądań. Każda aplikacja hostowana samodzielnie dzieli swój stan w inny sposób. Dlatego dwa serwery zdjęć wykonujące to samo zadanie nadal wymagają różnych poleceń tworzenia kopii zapasowej. Przed zaufaniem zrzutowi należy sprawdzić, jakie dane przechowuje aplikacja. Na drugim końcu tego zakresu znajduje się rozwiązanie podobne do biblioteki Jellyfin odbudowanej jako wypożyczalnia wideo z lat 90., które odczytuje cały katalog z innej usługi. W takim przypadku zwykle wystarczy skopiować własną konfigurację. mem0 wymaga natomiast obu baz danych, ponieważ w przeciwnym razie przywrócenie będzie bezużyteczne.

Przywrócenie tylko postgres spowoduje odzyskanie pamięci, ale wszystkie konta i klucze API znikną, przez co żaden użytkownik nie będzie mógł się uwierzytelnić, aby je odczytać. Należy zrzucić obie bazy oraz role w jednym poleceniu:

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

Wolumen historii jest oddzielony od Postgres i wymaga osobnej kopii:

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

Docker dodaje przedrostek nazwy projektu do nazw wolumenów, dlatego należy potwierdzić własną nazwę za pomocą docker volume ls przed założeniem, że jest to mem0_mem0_history.

Przywróć dane do kontenera tymczasowego i sprawdź liczbę wierszy, zanim uznasz proces za zakończony:

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

Kopia zapasowa, której nigdy nie przywrócono, jest tylko przypuszczeniem. Gdy zrzuty będą poprawne, należy przenieść je poza serwer za pomocą migawek restic do pamięci zewnętrznej, ponieważ kopia zapasowa znajdująca się na serwerze, który chroni, nie zapewnia żadnej ochrony.

Tryby awarii i dokładne komunikaty, które zobaczysz

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} oznacza, że nagłówek jest nieobecny lub zawiera błąd w pisowni. Nazwa to X-API-Key, a curl przesyła nazwy nagłówków w sposób dosłowny.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} przy operacji add oznacza, że żądanie nie zawierało żadnego z nich. Pamięć musi być przypisana do określonego zakresu, ponieważ filtry wyszukiwania działają dokładnie na tych polach.

LLM provider 'ollama' is not bundled in this image z kodem HTTP 400 oznacza, że wysłano "provider": "ollama". Użyj "provider": "openai" z parametrem openai_base_url skierowanym na Ollama.

expected 1536 dimensions, not 768 z Postgres oznacza, że kolekcja została utworzona z określoną szerokością, a model osadzający (embedder) zwraca inną. Ustaw embedding_model_dims w magazynie wektorów i użyj nowej collection_name.

Wyszukiwanie zwraca nielogiczne wiersze po zmianie modelu, bez żadnych błędów w logach. Szerokość nadal się zgadza, więc baza danych działa poprawnie, jednak dwa różne modele umieszczają to samo zdanie w różnych pozycjach. Utwórz nową kolekcję i dodaj dane ponownie.

Connection refused w logach mem0 podczas łączenia z Ollama zazwyczaj oznacza 127.0.0.1 w openai_base_url. Wewnątrz kontenera ten adres wskazuje na sam kontener. Użyj nazwy usługi lub host gateway, jeśli Ollama działa na hoście.

504 Gateway Time-out z nginx przy operacji add oznacza, że model działał dłużej niż proxy_read_timeout. Zwiększ ten limit i sprawdź, czy pamięć została zapisana przed ponowieniem żądania.

exit code 137 podczas docker compose up --build oznacza, że mechanizm out-of-memory killer przerywa budowanie dashboardu. Dodaj swap lub zbuduj obraz na maszynie o większych zasobach i wypchnij go do rejestru.

error: port 3000 is already in use pochodzi z celu make up w repozytorium, który odmawia uruchomienia, gdy porty 3000 lub 8888 są zajęte. Znajdź proces korzystający z portu za pomocą lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Czy nadal potrzebuję Neo4j, aby uruchomić mem0 z pamięcią grafową?

Nie. Nowy algorytm pamięci, wydany w kwietniu 2026 roku, usunął klucze konfiguracyjne graph_store oraz enable_graph z otwartego SDK. Ekstrakcja encji odbywa się teraz podczas standardowego dodawania i zapisuje dane do drugiej kolekcji pgvector o nazwie <collection_name>_entities, więc nie ma potrzeby używania zewnętrznej bazy grafowej, dodatkowego kontenera ani przeprowadzania migracji. Ceną za to jest brak pola relations w wynikach wyszukiwania. Encje podnoszą teraz ranking pamięci zamiast udostępniać krawędzie do przeglądania, więc aplikacja, która korzystała z tych relacji, musi posiadać własny magazyn grafowy poza mem0.

Jaki jest najmniejszy VPS, na którym można uruchomić samodzielnie hostowany serwer mem0?

Jeśli model językowy jest hostowany zewnętrznie, 2 GB pamięci RAM i około 4 GB wolnego miejsca na dysku wystarczą dla kontenera API, Postgres oraz panelu sterowania. Krytycznym momentem jest pierwsza kompilacja, ponieważ budowanie panelu Next.js zużywa więcej pamięci niż jego uruchomienie, a na maszynie z 1 GB RAM proces zostanie przerwany z błędem exit code 137. Jeśli Ollama działa na tym samym serwerze, należy uwzględnić rozmiar modelu: model 8B przy kwantyzacji 4-bitowej wymaga około 6 GB pamięci, więc należy zaplanować 8 GB RAM.

Czy mogę uruchomić mem0 bez klucza API OpenAI?

Tak, korzystając z punktu końcowego Ollama kompatybilnego z OpenAI. Ustawienie "provider": "ollama" kończy się niepowodzeniem, ponieważ obraz serwera zawiera tylko biblioteki openai, anthropic i gemini, co skutkuje błędem HTTP 400. Zamiast tego należy zachować "provider": "openai" i ustawić "openai_base_url": "http://ollama:11434/v1" na dowolną niepustą wartość api_key, zarówno dla llm, jak i embedder. Ollama ignoruje klucz, a weryfikacja dostawcy przechodzi pomyślnie, ponieważ dostawcą jest faktycznie openai.

Dlaczego mem0 nie zwraca wyników po przełączeniu na lokalny model osadzeń (embedding)?

Ponieważ tabela pgvector została utworzona ze stałą szerokością. embedding_model_dims domyślnie wynosi 1536, nomic-embed-text zwraca 768, a Postgres odrzuca wstawianie danych z błędem expected 1536 dimensions, not 768. mem0 tworzy tabelę z parametrem CREATE TABLE IF NOT EXISTS, więc sama zmiana liczby nie wpływa na istniejącą kolekcję. Należy ustawić embedding_model_dims na rzeczywistą szerokość modelu, potwierdzić tę szerokość wywołując /v1/embeddings i zliczając zwracane wartości, a następnie nadać magazynowi wektorów nową nazwę collection_name w tym samym czasie.