VPS에 mem0 직접 호스팅하기: 메모리 요구사항과 구축 가이드
mem0 메모리 서버를 VPS에 직접 호스팅하는 방법을 상세히 설명합니다. 1GB RAM 환경에서의 컨테이너 구성, Ollama 로컬 모델 실행 시 필요한 최소 8GB 사양, 그리고 빌드 중 발생하는 OOM(out-of-memory) 오류 해결을 위한 실전 가이드를 제공합니다.
VPS에서 mem0를 직접 호스팅할 때 필요한 실제 RAM 비용
mem0를 직접 호스팅한다는 것은 FastAPI 메모리 서버, pgvector 확장이 포함된 Postgres, 그리고 Next.js 대시보드라는 세 개의 컨테이너를 실행하는 것을 의미합니다. mem0는 에이전트를 위한 메모리 계층입니다. 대화 내용을 mem0에 전송하면 언어 모델이 그 대화에서 지속적인 사실을 추출하고, 이 사실들을 벡터로 저장하여 나중에 쿼리할 때 관련 내용을 다시 불러올 수 있게 합니다.
세 컨테이너를 위해 약 1 GB의 상주 메모리(resident memory)를 예산으로 잡고, 이미지가 빌드된 후에는 3~4 GB의 디스크 공간을 확보하십시오. 언어 모델이 다른 곳에 있다면 2 GB VPS로도 충분히 원활하게 실행됩니다. Ollama를 통해 같은 서버에서 모델을 실행할 경우 모델이 다른 모든 자원을 압도합니다. 4비트로 양자화된 8B 모델은 그 자체로 약 6 GB의 메모리를 요구하므로, 완전히 로컬에서 구축하려면 최소 8 GB가 필요합니다.
이 글을 포함하여 블로그 게시물에 적힌 수치를 그대로 믿지 마십시오. 실제로 구축한 스택을 직접 측정하십시오.
docker compose ps
docker stats --no-stream
docker system df -vdocker stats은 컨테이너별 상주 메모리를 출력합니다. docker system df -v는 각 이미지와 볼륨이 차지하는 디스크 용량을 출력합니다.
정상 상태(steady state)가 곧 최대 사용량은 아닙니다. docker compose up -d --build은 Next.js 대시보드를 컴파일하는데, 이 Node 빌드 과정이 전체 설치 과정에서 가장 많은 자원을 소모합니다. 1 GB VPS에서는 커널의 OOM(out-of-memory) 킬러가 이를 중단시키며 빌드가 exit code 137로 종료됩니다. Docker 버그를 의심하기 전에 먼저 원인을 확인하십시오:
dmesg -T | grep -i "killed process"서버 운영이 필요한 것보다 과도한 작업처럼 느껴진다면, 더 작은 대안들도 존재합니다. 서버가 전혀 없는 로컬 에이전트 메모리 저장소와 Claude Code 내부에 상주하는 메모리는 모두 데이터베이스를 사용하지 않습니다. 여러 에이전트나 여러 머신이 동일한 메모리를 읽어야 할 때 다시 이 글을 찾아오십시오.
mem0 그래프 메모리를 위해 Neo4j가 필요한가요?
아닙니다. 만약 Neo4j 컨테이너를 추가하라는 가이드가 있다면, 해당 가이드는 현재 코드보다 오래된 것입니다.
mem0의 그래프 메모리는 과거에 graph_store 키 아래 enable_graph을 true로 설정하여 외부 그래프 데이터베이스를 사용하는 것을 의미했습니다. 2026년 4월에 배포된 새로운 메모리 알고리즘은 오픈 소스 SDK에서 이 두 키를 모두 제거했습니다. 이제 엔티티 추출은 일반적인 add 경로 내에서 실행되며, 엔티티는 메인 컬렉션 이름 뒤에 _entities가 붙은 두 번째 pgvector 컬렉션에 기록됩니다. 별도의 마이그레이션은 필요하지 않습니다. 내장된 엔티티 연결 기능은 다음 add 호출부터 즉시 작동합니다.
그래프 저장소를 제거하면 JVM 컨테이너, 해당 힙 메모리, 그리고 수백 메가바이트의 이미지 용량을 절약할 수 있습니다. 2 GB VPS 환경에서는 이 차이가 서비스 실행 여부와 스왑 발생 여부를 결정짓습니다.
포기해야 하는 기능은 다음과 같습니다. 과거 검색 결과에는 엔티티 간의 관계를 나열하는 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입니다. 이 명령은 시작할 때마다 PyPI에서mem0ai을 재설치하므로, 업그레이드라고 생각하지 않은 재시작 과정에서 서버가 실행하는 버전이 바뀔 수 있습니다. - 동일한 pip 설치 단계로 인해, 외부 네트워크 연결이 없는 상태에서 재시작하면 uvicorn이 실행되기도 전에 실패합니다. PyPI에 접근할 수 없으면 메모리 서버는 다운됩니다.
--reload는 uvicorn의 파일 감시 기능을 시작합니다. 이 기능은 코드를 수정할 때 프로세스를 재시작하기 위한 것이며, 운영 환경에서는 아무런 이점 없이 메모리와 추가 프로세스만 소모합니다. 운영용Dockerfile의CMD에도--reload가 포함되어 있으므로, 어떤 방식으로든 명령을 재정의해야 합니다.- 공개된 포트는
"8888:8000","8432:5432","3000:3000"입니다. 앞에 주소가 지정되지 않은 공개 포트는0.0.0.0에 바인딩되므로, 스택이 시작되는 즉시 Postgres가 8432 포트를 통해 공용 인터넷의 요청에 응답하게 됩니다.
마지막 항목은 별도의 주의가 필요합니다. Docker는 ufw가 관리하는 체인보다 앞서 자체 규칙을 작성하여 포트를 공개하므로, ufw deny 8432을 사용해도 공개된 컨테이너 포트는 닫히지 않습니다. ufw를 우회하여 포트를 공개하는 Docker에서 관련 규칙을 자세히 설명합니다.
실제 서버를 위한 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여기에는 5가지 중요한 변경 사항이 있으며, 각각의 이유는 다음과 같습니다.
모든 ports 항목은 127.0.0.1로 시작하므로, 커널은 해당 연결을 서버 내부에서 발생하는 요청으로만 허용합니다. 외부에서 들어오는 모든 트래픽은 리버스 프록시를 통하며, 인증서를 보유한 유일한 지점도 리버스 프록시입니다.
Postgres에는 ports 블록이 전혀 없습니다. mem0 컨테이너는 서비스 이름을 통해 mem0_network로 접근하므로, 8432 포트를 공개하는 것은 아무런 이득이 없으며 불필요한 포트만 개방하게 됩니다. 셸이 필요할 때는 docker compose exec postgres psql -U postgres을 사용하십시오.
기록 데이터는 ./history 바인드 마운트에서 명명된 볼륨(named volume)으로 이동합니다. 바인드 마운트는 데이터를 호스트의 특정 경로와 특정 uid에 고정하지만, 명명된 볼륨은 Docker가 스냅샷을 찍거나 이동시킬 수 있는 객체입니다. 명명된 볼륨과 바인드 마운트 비교에서 각각의 적절한 사용 사례를 다룹니다.
명령어에서 --reload을 제거하고 alembic upgrade head을 유지하십시오. 해당 마이그레이션 단계를 반드시 유지해야 합니다. 그렇지 않으면 애플리케이션이 테이블이 없는 데이터베이스를 참조하여 실행되며, 모든 요청이 첫 번째 쿼리에서 실패합니다.
NEXT_PUBLIC_API_URL는 브라우저가 호출하는 URL이므로, http://mem0:8000이 아닌 공용 HTTPS 주소여야 합니다. 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 .envPOSTGRES_PASSWORD, JWT_SECRET, ADMIN_API_KEY을 설정합니다. AUTH_DISABLED=false는 그대로 둡니다. 이 플래그의 이름은 그 기능을 정직하게 나타냅니다. 이 플래그를 켜면 서버는 해당 포트에 접근할 수 있는 누구에게나 보유한 모든 메모리 정보를 전달합니다. 온보딩 이벤트를 업스트림으로 전송하지 않으려면 MEM0_TELEMETRY=false을 설정하십시오.
ADMIN_API_KEY은 secrets.compare_digest을 사용하여 X-API-Key 헤더와 비교되며, 일치할 경우 모든 데이터베이스 조회를 건너뜁니다. 이는 전체 API에 대한 루트 자격 증명입니다. 셸 히스토리나 git에 남기지 말고, 프롬프트에 붙여넣지 않는 등 루트 자격 증명처럼 취급하십시오. Compose 환경 파일과 비밀 정보가 유출되는 경로 및 에이전트 컨텍스트에서 API 키를 제외하는 방법 문서는 이 서버의 호출자가 에이전트이므로 직접적으로 적용됩니다.
env_file에서 로드된 값은 컨테이너 환경에 상주하며, docker inspect는 이를 전체 출력합니다. docker 그룹에 속한 사람은 누구나 이 값을 읽을 수 있으며, docker 그룹에 속한 사람은 사실상 호스트의 루트 권한을 가집니다.
8888 포트를 여는 대신 API 앞에 TLS를 배치하십시오
API는 127.0.0.1:8888에서 응답하고 대시보드는 127.0.0.1:3000에서 응답합니다. nginx는 443 포트에서 TLS(transport layer security)를 종료하고 두 서비스로 요청을 전달합니다.
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 호출은 언어 모델이 대화를 읽고 사실 관계를 추출하는 동안 차단(block) 상태가 됩니다. CPU에서 구동되는 로컬 8B 모델은 nginx의 기본 제한 시간인 60초보다 더 오래 걸리는 경우가 많습니다. 이 경우 모델이 여전히 작업 중이고 메모리가 기록되고 있음에도 호출자는 504 Gateway Time-out 오류를 보게 됩니다. 결과적으로 실패했다고 안내받은 메모리가 실제로는 저장되는 상황이 발생합니다.
기본 거부 ufw 정책을 사용하여 나머지 포트를 닫고 22번과 443번 포트만 개방하십시오. Ubuntu 24.04의 nginx 환경에서 certbot을 사용하여 인증서를 발급받으십시오. 만약 해당 서버가 이미 여러 Compose 앱을 라우팅하는 Traefik을 통해 다른 앱을 서비스하고 있다면, 두 번째 프록시를 설치하는 대신 해당 라우터에 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"}'정상적인 응답은 results 목록을 포함하는 JSON 객체이며, 각 항목은 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에 관한 사실은 점수와 함께 반환되어야 합니다. 예시와 같이 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의 기본값은 100에서 20으로 낮아졌으며, threshold의 기본값은 '없음'이 아닌 0.1로 설정되어 있어 약한 일치 항목은 자동으로 필터링됩니다. curl을 통해 이 과정이 정상적으로 작동하면, 동일한 엔드포인트를 에이전트에 연결하십시오. 직접 연결하거나 동일한 VPS에서 실행 중인 MCP 서버를 통해 연결할 수 있습니다.
OpenAI 키 없이 mem0 실행하기
첫 5분 안에 마주하게 될 차단 요소부터 다룹니다. 서버 이미지는 고정된 공급자 라이브러리 세트를 포함하며, /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는 /v1에서 OpenAI 호환 API를 제공하며, /v1/chat/completions 및 /v1/embeddings를 지원합니다. mem0의 openai 공급자는 openai_base_url를 허용합니다. 해당 키를 Ollama로 지정하면 공급자가 실제로 openai이므로 번들로 제공된 검사를 통과합니다. 주소만 변경하면 됩니다.
동일한 Compose 프로젝트에 Ollama를 추가합니다:
ollama:
image: ollama/ollama
restart: unless-stopped
networks: [mem0_network]
ports:
- "127.0.0.1:11434:11434"
volumes:
- ollama_models:/root/.ollama최상위 volumes: 키 아래에 ollama_models:을 추가한 다음, 채팅 모델 하나와 임베딩 모델 하나를 가져옵니다(pull):
docker compose up -d ollama
docker compose exec ollama ollama pull llama3.1:8b
docker compose exec ollama ollama pull nomic-embed-textOllama를 VPS에서 직접 실행하기에서처럼 Ollama가 이미 호스트에서 systemd 유닛으로 실행 중이라면, 컨테이너를 127.0.0.1:11434로 지정하지 마십시오. mem0 컨테이너 내부에서 127.0.0.1은 mem0 컨테이너 자신을 의미합니다. mem0 서비스에 extra_hosts: ["host.docker.internal:host-gateway"]을 부여하고, systemd 드롭인 파일에 Environment="OLLAMA_HOST=0.0.0.0:11434"를 설정하여 Ollama가 브리지 네트워크에서 접근 가능한 주소에서 수신 대기하도록 하며, 방화벽에서 11434 포트는 닫아 두십시오.
설정 전 모델의 임베딩 차원 확인하기
이 단계는 검색 기능의 정상 작동 여부를 결정합니다.
mem0의 pgvector 저장소는 고정된 벡터 너비인 vector vector(1536)으로 테이블을 생성합니다. embedding_model_dims는 기본값이 1536인데, 이는 OpenAI의 text-embedding-3-small 너비입니다. 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은 embedding_dims를 설정할 때만 OpenAI dimensions 매개변수를 전송하는데, Matryoshka 자르기를 구현하지 않은 백엔드는 해당 매개변수를 즉시 거부합니다. 테이블이 생성되는 곳에 너비를 설정하고 임베더는 그대로 두십시오.
collection_name은 새로운 설정입니다. mem0은 CREATE TABLE IF NOT EXISTS로 테이블을 생성하므로, 기존 컬렉션에 다른 너비를 지정해도 아무런 효과가 없습니다. 기존 vector(1536) 컬럼이 그대로 유지되어 모든 삽입이 실패합니다. 너비를 변경하려면 새로운 컬렉션 이름을 사용하거나, 기존 테이블을 직접 삭제해야 합니다.
openai_base_url의 호스트는 localhost이 아니라 Compose 서비스 이름인 ollama입니다. 컨테이너는 공유 네트워크상에서 서비스 이름으로 서로를 식별합니다.
완전 로컬 경로의 비용
품질에 대해 솔직해질 필요가 있습니다. mem0의 공개된 벤치마크 점수는 최첨단 모델을 사용하여 추출한 결과이므로, VPS에서 실행하는 8B 모델에 대한 예측치가 아닌 상한선으로 간주하십시오. 작은 모델은 더 모호한 사실을 기록하며, JSON을 요청했을 때 산문을 반환하기도 합니다. 이는 add 호출이 오류 없이 빈 results 리스트를 반환하는 현상으로 나타납니다.
속도 또한 비용입니다. CPU 전용 추출은 add 호출당 수 초가 소요되며, 저장하는 모든 메시지에 이 비용이 발생합니다. 지연 시간이 중요하다면 GPU가 장착된 VPS를 사용하는 것이 올바른 해결책입니다. 8B 모델에 CPU 코어를 더 추가하는 것은 기대보다 효과가 훨씬 적습니다.
어떤 선택을 하든 한 가지 규칙은 반드시 지켜야 합니다. 하나의 컬렉션 내에서 임베딩 모델을 혼용하지 마십시오. 우연히 너비가 같은 두 개의 서로 다른 모델은 비교 불가능한 벡터를 생성합니다. 삽입은 성공하고 검색은 행을 반환하지만, 그 결과는 잘못된 것이며 어디에서도 오류를 보고하지 않습니다.
백업: 데이터베이스는 하나가 아니라 두 개입니다
가장 흔한 mem0 백업 실수는 데이터베이스 하나만 덤프하는 것입니다. init-db.sh은 기본 postgres 데이터베이스와 함께 mem0_app을 생성하며, 이 둘은 서로 다른 데이터를 저장합니다. postgres 데이터베이스는 기억에 해당하는 pgvector 컬렉션을 보관합니다. mem0_app는 사용자, 세션, API 키 및 요청 로그를 보관합니다.
postgres만 복원하면 기억은 돌아오지만 모든 계정과 API 키가 사라지므로, 아무도 인증을 거쳐 데이터를 읽을 수 없게 됩니다. 다음 명령어를 사용하여 역할(roles)을 포함한 두 데이터베이스를 한 번에 덤프하십시오.
docker compose exec -T postgres pg_dumpall -U postgres --clean \
| gzip > "mem0-$(date +%F).sql.gz"히스토리 볼륨은 Postgres와 별개이므로 별도의 복사본이 필요합니다.
docker run --rm -v mem0_mem0_history:/data -v "$PWD:/backup" \
alpine tar czf /backup/mem0-history.tgz -C /data .Docker는 볼륨 이름 앞에 프로젝트 이름을 붙이므로, mem0_mem0_history이라고 가정하기 전에 docker volume ls으로 실제 이름을 확인하십시오.
덤프를 신뢰하기 전에 스크래치 컨테이너에 복원하여 행 개수를 확인하십시오.
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."}은 요청에 해당 필드가 없음을 의미합니다. 검색은 정확히 해당 필드를 대상으로 필터링하므로, 메모리는 반드시 특정 대상에 범위가 지정되어야 합니다.
HTTP 400과 함께 발생하는 LLM provider 'ollama' is not bundled in this image은 "provider": "ollama"를 전송했음을 의미합니다. openai_base_url가 Ollama를 가리키도록 설정한 상태에서 "provider": "openai"을 사용하십시오.
Postgres에서 발생하는 expected 1536 dimensions, not 768는 컬렉션이 생성된 너비와 임베더가 반환하는 너비가 다를 때 발생합니다. 벡터 저장소에서 embedding_model_dims을 설정하고 새로운 collection_name을 사용하십시오.
모델 변경 후 검색 결과가 의미 없는 행을 반환하는 경우, 별도의 오류는 발생하지 않습니다. 데이터베이스 관점에서는 너비가 일치하므로 정상으로 처리되지만, 두 모델은 동일한 문장을 서로 다른 위치에 배치합니다. 새로운 컬렉션을 생성하고 데이터를 다시 추가하십시오.
Ollama에 연결하는 동안 mem0 로그에 나타나는 Connection refused은 보통 openai_base_url 내의 127.0.0.1를 의미합니다. 컨테이너 내부에서 해당 주소는 컨테이너 자신을 가리킵니다. 서비스 이름을 사용하거나, Ollama가 호스트에서 실행 중인 경우 host gateway를 사용하십시오.
추가 작업 시 nginx에서 발생하는 504 Gateway Time-out은 모델이 proxy_read_timeout보다 더 오랜 시간을 소요했음을 의미합니다. 제한 시간을 늘리고, 요청을 재시도하기 전에 메모리가 이미 기록되었는지 확인하십시오.
docker compose up --build 도중 발생하는 exit code 137은 OOM(Out-of-Memory) 킬러가 대시보드 빌드를 중단시킨 것입니다. 스왑을 추가하거나, 더 큰 사양의 머신에서 이미지를 빌드하여 레지스트리에 푸시하십시오.
error: port 3000 is already in use는 리포지토리의 make up 타겟에서 발생하며, 3000번 또는 8888번 포트가 사용 중일 때 시작을 거부합니다. lsof -iTCP:3000 -sTCP:LISTEN을 사용하여 해당 포트를 점유 중인 프로세스를 찾으십시오.
FAQ
그래프 메모리를 사용하여 mem0을 실행하려면 여전히 Neo4j가 필요한가요?
아니요. 2026년 4월에 배포된 새로운 메모리 알고리즘은 오픈 소스 SDK에서 graph_store 및 enable_graph 설정 키를 제거했습니다. 이제 엔티티 추출은 일반적인 추가 작업 중에 실행되며 <collection_name>_entities이라는 이름의 두 번째 pgvector 컬렉션에 기록됩니다. 따라서 외부 그래프 데이터베이스나 추가 컨테이너, 마이그레이션 단계가 필요하지 않습니다. 그 대가로 검색 결과의 relations 필드는 더 이상 존재하지 않습니다. 이제 엔티티는 탐색할 에지를 제공하는 대신 메모리의 순위를 높이는 역할을 하므로, 해당 관계를 탐색하던 애플리케이션은 mem0 외부에서 자체적인 그래프 저장소를 사용해야 합니다.
mem0 서버를 자체 호스팅하기 위한 최소 사양의 VPS는 무엇인가요?
언어 모델을 다른 곳에서 호스팅하는 경우, API 컨테이너, Postgres, 대시보드를 실행하는 데 2 GB의 RAM과 약 4 GB의 여유 디스크 공간이면 충분합니다. 가장 까다로운 순간은 첫 빌드 시점입니다. Next.js 대시보드를 컴파일하는 것이 실행하는 것보다 더 많은 메모리를 사용하기 때문에, 1 GB 사양의 서버에서는 exit code 137 오류로 인해 빌드가 중단됩니다. 만약 Ollama를 같은 서버에서 실행한다면 모델 크기를 고려해야 합니다. 4-bit 양자화된 8B 모델은 그 자체로 약 6 GB가 필요하므로 8 GB 사양을 계획하십시오.
OpenAI API 키 없이 mem0을 실행할 수 있나요?
네, Ollama의 OpenAI 호환 엔드포인트를 통해 가능합니다. "provider": "ollama"을 설정하면 실패하는데, 이는 서버 이미지가 openai, anthropic, gemini 라이브러리만 포함하고 있어 HTTP 400을 반환하기 때문입니다. 대신 "provider": "openai"를 유지하고 llm과 임베더 모두에 대해 비어 있지 않은 임의의 api_key으로 "openai_base_url": "http://ollama:11434/v1"를 설정하십시오. 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을 부여하십시오.