SSD Nodes Learn 🎉 VPS от $4.99/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-07

Self-hosting mem0 на VPS: настройка и системные требования

Руководство по запуску mem0 на своем сервере. Узнайте реальный расход RAM для стека FastAPI, Postgres и Next.js, а также как настроить Docker Compose с TLS и локальным Ollama.

Реальные затраты оперативной памяти при self-hosting mem0 на VPS

Self-hosting mem0 подразумевает запуск трех контейнеров: сервера памяти на FastAPI, базы данных Postgres с расширением pgvector и панели управления на Next.js. mem0 — это уровень памяти для агентов. Вы отправляете в него диалог, языковая модель извлекает из него устойчивые факты, которые сохраняются в виде векторов, чтобы последующий запрос мог получить соответствующие данные обратно.

Закладывайте примерно 1 ГБ оперативной памяти для этих трех контейнеров и от 3 до 4 ГБ дискового пространства после сборки образов. VPS с 2 ГБ RAM комфортно справится с этой задачей, если языковая модель работает на другом узле. Если модель запускается на том же сервере через Ollama, она потребляет основные ресурсы: 8B модель с квантованием 4 бита требует около 6 ГБ RAM, поэтому для полностью локальной сборки потребуется от 8 ГБ.

Не полагайтесь на цифры из блогов, включая этот. Измеряйте стек, который вы развернули на самом деле.

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

docker stats выводит объем резидентной памяти для каждого контейнера. docker system df -v показывает объем дискового пространства, занимаемый каждым образом и томом.

Установившееся состояние — это не пиковая нагрузка. docker compose up -d --build компилирует панель управления Next.js, и этот процесс сборки на Node является самым ресурсоемким этапом всей установки. На VPS с 1 ГБ RAM механизм OOM killer (out-of-memory killer) ядра принудительно завершит процесс, и сборка закончится ошибкой exit code 137. Подтвердите причину, прежде чем искать ошибку в Docker:

dmesg -T | grep -i "killed process"

Если сервер кажется избыточным решением для ваших задач, существуют более компактные варианты. локальное хранилище памяти агента без использования сервера и память, встроенная непосредственно в Claude Code позволяют обойтись без базы данных. Возвращайтесь к этому руководству, когда нескольким агентам или нескольким машинам потребуется доступ к одним и тем же данным памяти.

Нужен ли Neo4j для графовой памяти mem0?

Нет. Если в руководстве указано добавить контейнер Neo4j, значит, это руководство устарело.

Ранее графовая память в mem0 подразумевала использование внешней графовой базы данных, которая настраивалась через ключ graph_store при значении enable_graph, равном true. Новый алгоритм работы с памятью, выпущенный в апреле 2026 года, исключил оба этих ключа из SDK с открытым исходным кодом. Извлечение сущностей теперь выполняется в рамках стандартного пути добавления данных, а сами сущности записываются во вторую коллекцию pgvector, название которой совпадает с основной с добавлением суффикса _entities. Процедура миграции не требуется. Встроенная функция связывания сущностей начинает работать при следующем вызове команды добавления.

Отказ от графового хранилища позволяет не запускать контейнер JVM, не выделять память под его кучу (heap) и экономит несколько сотен мегабайт дискового пространства. На VPS с 2 GB оперативной памяти это определяет разницу между нормальной работой и уходом системы в swap.

Ниже перечислены возможности, от которых вы отказываетесь. Результаты поиска ранее содержали поле relations, в котором перечислялись ребра между сущностями. Это поле удалено. Совпадения сущностей теперь просто повышают позицию записи в памяти в общем рейтинге, а структуры, по которой можно было бы перемещаться, больше нет. Если ваше приложение использовало эти связи, mem0 больше их не хранит. В таком случае вам потребуется собственная графовая база данных вне mem0, которую вы будете наполнять своим кодом.

Файл compose в репозитории предназначен для разработки

server/docker-compose.yaml объявляет name: mem0-dev, и это именно то, что он делает. Прочитайте его перед запуском, так как пять моментов в нем не подходят для сервера.

  • Он выполняет сборку из server/dev.Dockerfile и монтирует вашу рабочую копию поверх образа с помощью .:/app, поэтому контейнер запускает то, что находится в этой директории, а не то, что вы собрали.
  • Его команда — rm -rf /app/packages && pip install -q --force-reinstall --no-deps mem0ai && alembic upgrade head && uvicorn main:app --reload. Она переустанавливает mem0ai из PyPI при каждом запуске, поэтому версия, которую использует ваш сервер, может измениться во время перезагрузки, которую вы не планировали как обновление.
  • Тот же шаг с pip означает, что перезагрузка при отсутствии исходящего сетевого соединения завершится ошибкой еще до запуска uvicorn. Ваш сервер памяти окажется недоступен, так как PyPI был недосягаем.
  • --reload запускает механизм отслеживания файлов uvicorn. Он нужен для перезапуска процесса при редактировании кода, но в продакшене он лишь потребляет память и создает лишний процесс, не принося пользы. В продакшене Dockerfile также содержит --reload в своем CMD, поэтому вы в любом случае переопределяете команду.
  • Опубликованные порты — "8888:8000", "8432:5432" и "3000:3000". Опубликованный порт без указания адреса привязывается к 0.0.0.0, поэтому Postgres начинает отвечать всему интернету на порту 8432 сразу после запуска стека.

Последний пункт заслуживает отдельного предупреждения. Docker публикует порт, записывая свои правила до цепочки, которой управляет ufw, поэтому ufw deny 8432 не закрывает опубликованный порт контейнера. В Docker publishing ports straight past ufw подробно описаны соответствующие правила.

Файл compose для реального сервера

Работайте внутри server/, сохраните init-db.sh на месте и замените docker-compose.yaml на следующее.

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

Здесь важны пять изменений, и у каждого есть причина.

Каждая запись ports начинается с 127.0.0.1, поэтому ядро принимает эти соединения только с самого сервера. Весь внешний трафик поступает через обратный прокси-сервер, который является единственным компонентом, хранящим сертификат.

У Postgres вообще нет блока ports. Контейнер mem0 обращается к нему по mem0_network, используя имя сервиса, поэтому публикация порта 8432 ничего не дает, кроме открытого порта. Используйте docker compose exec postgres psql -U postgres, если вам нужен доступ к оболочке (shell).

История перемещается из bind mount ./history в именованный том. Bind mount привязывает данные к конкретному пути и конкретному uid на хосте, в то время как именованный том — это объект, который Docker может копировать и переносить. В Именованные тома против bind mounts описано, когда и что лучше использовать.

Команда удаляет --reload и оставляет alembic upgrade head. Обязательно сохраните этот шаг миграции. Без него приложение запустится с базой данных без таблиц, и каждый запрос будет завершаться ошибкой при первой же попытке обращения.

NEXT_PUBLIC_API_URL — это URL, который вызывает ваш браузер, поэтому он должен быть публичным HTTPS-адресом, а не http://mem0:8000. Next.js встраивает каждое значение NEXT_PUBLIC_ во время сборки, поэтому для его изменения требуется docker compose up -d --build mem0-dashboard. Обычный перезапуск сохранит старое значение, «запеченное» в JavaScript, и панель управления будет обращаться к неверному хосту.

Секреты хранятся в .env, и .env не должен попадать в интернет

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

Установите POSTGRES_PASSWORD, JWT_SECRET и ADMIN_API_KEY. Оставьте AUTH_DISABLED=false. Название флага точно описывает его действие: при включении сервер передает всю память, к которой имеет доступ, любому, кто может подключиться к порту. Установите MEM0_TELEMETRY=false, если вы не хотите, чтобы событие онбординга отправлялось вышестоящему узлу.

ADMIN_API_KEY сравнивается с заголовком X-API-Key с помощью secrets.compare_digest, и совпадение позволяет пропустить все запросы к базе данных. Это корневой (root) ключ доступа ко всему API. Обращайтесь с ним соответствующим образом: не сохраняйте в истории shell, не добавляйте в git, не вставляйте в командную строку. Статьи Файлы окружения Compose и утечки секретов из них и защита API-ключей от попадания в контекст агента применимы напрямую, так как вызывающие стороны этого сервера — агенты.

Значения, загруженные из env_file, находятся в окружении контейнера, а docker inspect выводит их в полном объеме. Любой пользователь в группе docker может прочитать их, а любой пользователь в группе docker фактически обладает правами root на хосте.

Размещение TLS перед API вместо открытия порта 8888

API отвечает на 127.0.0.1:8888, а панель управления — на 127.0.0.1:3000. nginx выполняет TLS (transport layer security) termination на порту 443 и перенаправляет запросы к обоим сервисам.

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 важнее, чем кажется на первый взгляд. Вызов add блокируется, пока языковая модель считывает диалог и извлекает факты. Локальная модель 8B на CPU часто работает дольше, чем стандартный таймаут nginx в 60 секунд. В результате вызывающая сторона получает 504 Gateway Time-out, хотя модель всё ещё продолжает работу, а данные записываются в память. В итоге вы получаете сообщение об ошибке записи в память, которая на самом деле была успешной.

Закройте остальные порты с помощью политики ufw по умолчанию deny, оставив открытыми только 22 и 443. Выпустите сертификат, используя certbot в Ubuntu 24.04 с nginx. Если сервер уже обслуживает другие приложения через маршрутизацию Traefik для нескольких Compose-проектов, добавьте mem0 в этот маршрутизатор вместо установки второго прокси-сервера.

Дымовое тестирование: добавление одной записи в память и её чтение

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

Корректный ответ представляет собой JSON-объект со списком results, где каждая запись содержит id, извлечённый текст memory и "event": "ADD". Текущий алгоритм возвращает только события ADD. События UPDATE и DELETE были удалены, поэтому их отсутствие не является ошибкой.

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

Факт о Postgres 17 должен вернуться вместе с оценкой (score). Передайте идентификатор внутри filters, как показано в примере. Использование user_id верхнего уровня по-прежнему работает, а сервер записывает Top-level user_id in /search is deprecated. Use filters={...} instead. в лог при каждом таком обращении.

Удалите тестовые данные после завершения, чтобы они не влияли на результаты реального поиска:

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

Если поиск возвращает меньше строк, чем ожидалось, проверьте значения по умолчанию, прежде чем искать проблему в механизме извлечения. В текущем релизе top_k по умолчанию равно 20 (ранее было 100), а threshold по умолчанию равно 0.1 вместо отсутствия фильтрации, поэтому слабые совпадения теперь отсеиваются автоматически. Как только вы добьетесь работы через curl, эти же эндпоинты можно подключать к агенту — напрямую или через MCP-сервер, запущенный на том же VPS.

Запуск mem0 без ключа OpenAI

Начните с блокирующего фактора, так как вы столкнетесь с ним в первые пять минут. Образ сервера поставляется с фиксированным набором библиотек провайдеров, и /configure отклоняет всё, что выходит за их рамки:

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.

Вам не нужно ничего пересобирать. Ollama предоставляет API, совместимый с OpenAI, по адресу /v1, поддерживая /v1/chat/completions и /v1/embeddings, а провайдер openai в mem0 принимает openai_base_url. Укажите этот ключ для Ollama, и встроенная проверка будет пройдена, так как провайдер действительно является openai. Меняется только адрес.

Добавьте Ollama в тот же проект Compose:

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

Добавьте ollama_models: в раздел volumes: верхнего уровня, а затем загрузите одну модель для чата и одну модель для эмбеддингов:

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

Если Ollama уже запущена на хосте как systemd-юнит, как описано в запуске Ollama напрямую на VPS, не указывайте контейнеру 127.0.0.1:11434. Внутри контейнера mem0 127.0.0.1 — это сам контейнер mem0. Предоставьте сервису mem0 extra_hosts: ["host.docker.internal:host-gateway"], установите Environment="OLLAMA_HOST=0.0.0.0:11434" в drop-in файле systemd, чтобы Ollama слушала адрес, доступный из моста, и оставьте порт 11434 закрытым на брандмауэре.

Узнайте размерность эмбеддингов модели перед настройкой

Этот шаг определяет, будет ли вообще работать поиск.

Хранилище pgvector в mem0 создает таблицу с фиксированной шириной вектора vector vector(1536), так как embedding_model_dims по умолчанию использует 1536 — ширину модели text-embedding-3-small от OpenAI. nomic-embed-text возвращает 768 значений. Внутри mem0 нет проверки этих двух чисел, поэтому несоответствие проявится со стороны Postgres при первой же попытке вставки:

expected 1536 dimensions, not 768

Не доверяйте числу в этом абзаце. Спросите модель:

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

Это выведет ширину, которую должна использовать ваша коллекция. Запишите конфигурацию в файл, так как передача пароля Postgres через экранирование в оболочке — верный способ допустить опечатку в продакшене.

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

Второй вызов считывает конфигурацию обратно, что подтверждает успешность записи. Затем повторите дымовой тест, описанный выше.

Четыре детали в этом JSON не очевидны, и каждая из них приведет к сбою при ошибке.

api_key — это строка ollama, и Ollama игнорирует её значение. Она не может быть пустой, так как клиентская библиотека OpenAI вызывает ошибку до отправки запроса, если ключ не задан. Подойдет любая непустая строка.

embedding_model_dims применяется к векторному хранилищу, и для эмбеддера намеренно не задается embedding_dims. mem0 отправляет параметр dimensions от OpenAI только при установке embedding_dims, а бэкенды, не реализующие усечение Matryoshka, отклоняют этот параметр. Установите ширину там, где создается таблица, и не трогайте эмбеддер.

collection_name — новый параметр. mem0 создает таблицу с CREATE TABLE IF NOT EXISTS, поэтому указание другой ширины для существующей коллекции ничего не даст: старый столбец vector(1536) останется, и каждая вставка будет завершаться ошибкой. Изменение ширины требует создания новой коллекции или ручного удаления старой таблицы.

Хост в openai_base_url — это имя сервиса в Compose ollama, а не localhost. Контейнеры находят друг друга по имени сервиса в общей сети.

Цена полностью локального пути

Будьте честны с собой в вопросе качества. Опубликованные результаты тестов mem0 были получены с использованием передовых моделей для извлечения данных, поэтому воспринимайте их как предел возможностей, а не как прогноз для модели 8B на вашем VPS. Маленькая модель пишет более расплывчатые факты и иногда возвращает текст там, где требовался JSON, что проявляется в виде вызова add, возвращающего пустой список results без ошибки.

Скорость — это вторая цена. Извлечение данных только на CPU занимает секунды на каждый вызов add, и каждое сохраняемое сообщение требует этих затрат. Если эта задержка критична, VPS с GPU — единственное честное решение. Увеличение количества ядер CPU для модели 8B помогает гораздо меньше, чем ожидается.

Одно правило действует независимо от вашего выбора: никогда не смешивайте модели эмбеддингов в одной коллекции. Две разные модели, случайно имеющие одинаковую ширину, создают векторы, которые невозможно сравнивать. Вставка пройдет успешно, поиск вернет строки, но эти строки будут неверными, при этом нигде не будет сообщений об ошибках.

Резервное копирование: две базы данных, а не одна

Самая частая ошибка при создании резервной копии mem0 — это дамп только одной базы данных. init-db.sh создает mem0_app наряду с базой данных postgres по умолчанию, и они содержат разные данные. В базе данных postgres хранятся коллекции pgvector, то есть «воспоминания». В mem0_app хранятся пользователи, сессии, API-ключи и логи запросов.

Если восстановить только postgres, воспоминания вернутся, но все учетные записи и API-ключи будут удалены, поэтому ничто не сможет пройти аутентификацию для их чтения. Создавайте дамп обеих баз данных, включая роли, одной командой:

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

Том с историей (history volume) отделен от Postgres и требует отдельного копирования:

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

Docker добавляет к именам томов префикс с названием проекта, поэтому уточните свои имена с помощью docker volume ls, прежде чем полагаться на mem0_mem0_history.

Выполните восстановление в пустой контейнер и проверьте количество строк, прежде чем считать данные достоверными:

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

Резервная копия, которую вы никогда не восстанавливали, — это лишь предположение. Как только дампы станут корректными, выгружайте их с сервера с помощью снимков restic в удаленное хранилище, так как резервная копия, хранящаяся на защищаемом сервере, не обеспечивает никакой защиты.

Режимы сбоев и точные сообщения, которые вы увидите

{"detail":"Authentication required. Provide a Bearer token or X-API-Key header."} означает, что заголовок отсутствует или написан с ошибкой. Имя должно быть X-API-Key, а curl передает имена заголовков буквально.

{"detail":"At least one identifier (user_id, agent_id, run_id) is required."} при добавлении означает, что в запросе не было ни одного из них. Память должна быть привязана к чему-либо, так как фильтры поиска работают именно по этим полям.

LLM provider 'ollama' is not bundled in this image с кодом HTTP 400 означает, что вы отправили "provider": "ollama". Используйте "provider": "openai" с openai_base_url, указывающим на Ollama.

expected 1536 dimensions, not 768 от Postgres означает, что коллекция была создана с одной размерностью, а эмбеддер возвращает другую. Установите embedding_model_dims в векторном хранилище и используйте новую collection_name.

Поиск возвращает бессмысленные строки после смены модели, при этом ошибки отсутствуют. Размерность совпадает, поэтому база данных работает корректно, но две модели размещают одно и то же предложение в разных позициях. Создайте новую коллекцию и добавьте данные заново.

Connection refused в логах mem0 при попытке обращения к Ollama обычно означает 127.0.0.1 в openai_base_url. Внутри контейнера этот адрес указывает на сам контейнер. Используйте имя сервиса или host gateway, если Ollama запущена на хосте.

504 Gateway Time-out от nginx при добавлении означает, что модель отвечала дольше, чем proxy_read_timeout. Увеличьте это значение и проверьте, была ли память записана до того, как вы повторите запрос.

exit code 137 во время docker compose up --build означает, что OOM killer остановил сборку панели управления. Добавьте swap или соберите образ на более мощной машине и отправьте его в реестр.

error: port 3000 is already in use исходит из цели make up в репозитории, которая отказывается запускаться, если порты 3000 или 8888 заняты. Найдите владельца процесса с помощью lsof -iTCP:3000 -sTCP:LISTEN.

FAQ

Нужно ли мне по-прежнему использовать Neo4j для работы mem0 с графовой памятью?

Нет. Новый алгоритм работы с памятью, выпущенный в апреле 2026 года, исключил ключи конфигурации graph_store и enable_graph из SDK с открытым исходным кодом. Извлечение сущностей теперь выполняется во время обычной операции добавления и записывается во вторую коллекцию pgvector с именем <collection_name>_entities, поэтому внешняя графовая база данных, дополнительный контейнер и этап миграции больше не требуются. Обратной стороной является то, что поле relations в результатах поиска больше не существует. Сущности теперь повышают рейтинг памяти, а не предоставляют ребра для обхода, поэтому приложению, которое использовало эти связи, потребуется собственное графовое хранилище вне mem0.

Какой минимальный размер VPS нужен для запуска self-hosted сервера mem0?

Если языковая модель размещена на другом ресурсе, для контейнера API, Postgres и панели управления достаточно 2 GB оперативной памяти и около 4 GB свободного места на диске. Критическим моментом является первая сборка, так как компиляция панели управления Next.js потребляет больше памяти, чем её работа, и на сервере с 1 GB процесс сборки будет завершен с ошибкой exit code 137. Если на том же сервере работает Ollama, учитывайте размер модели: модель 8B с 4-битной квантованием сама по себе требует около 6 GB, поэтому планируйте использование 8 GB.

Могу ли я запустить mem0 без API-ключа OpenAI?

Да, через совместимый с OpenAI эндпоинт Ollama. Установка "provider": "ollama" приведет к ошибке, так как образ сервера содержит только библиотеки openai, anthropic и gemini и возвращает HTTP 400. Вместо этого оставьте "provider": "openai" и установите "openai_base_url": "http://ollama:11434/v1" с любым непустым значением api_key как для llm, так и для embedder. Ollama игнорирует ключ, а проверка встроенного провайдера проходит успешно, так как провайдером действительно является openai.

Почему mem0 не возвращает результаты после переключения на локальную модель эмбеддингов?

Потому что таблица pgvector была создана с фиксированной шириной. embedding_model_dims по умолчанию имеет значение 1536, nomic-embed-text возвращает 768, и Postgres отклоняет вставку с ошибкой expected 1536 dimensions, not 768. mem0 создает таблицу с CREATE TABLE IF NOT EXISTS, поэтому изменение только числа не влияет на существующую коллекцию. Установите embedding_model_dims в соответствии с реальной шириной вашей модели, подтвердите эту ширину, вызвав /v1/embeddings и подсчитав количество возвращаемых значений, и одновременно задайте векторному хранилищу новое имя collection_name.