📚 1인 인프라 구축기 #3

CouchDB + Obsidian LiveSync로 메모 동기화 구축하기

Oracle ARM 서버에 CouchDB를 Docker로 올리고, Obsidian Self-hosted LiveSync 플러그인으로 실시간 메모 동기화를 구축하면서 만난 삽질들. CORS, max_document_size, 시스템 DB 누락까지 실전 트러블슈팅.

CouchDB + Obsidian LiveSync로 메모 동기화 구축하기

💡 Tip. 바쁜 현대인들을 위한 본문 요약

  • CouchDB + Obsidian LiveSync 조합으로 외부 서비스 없이 메모 실시간 동기화를 구축할 수 있다
  • Docker로 올린 뒤 시스템 DB(_users, _replicator)를 반드시 수동 생성해야 한다 — 빠뜨리면 500 에러
  • Obsidian 앱은 app://obsidian.md origin을 쓰므로 CouchDB 자체 CORS 설정이 필수 (Nginx 중복 금지)
  • 기본 max_document_size는 8MB — 이미지/첨부가 있다면 50MB 이상으로 확장 필요
  • 두 번째 기기 연결 시 “서버 초기화” 대신 반드시 “Fetch from Remote” 사용

Obsidian을 Windows와 Mac Mini 두 대에서 쓰고 있다.

Obsidian Sync? 월 $4. iCloud 동기화? Windows에서 안 된다. Git으로 동기화? 충돌 나면 미쳐버린다.

결국 CouchDB 기반의 Self-hosted LiveSync를 선택했다. CouchDB를 내 서버에 직접 올리고, 실시간 양방향 동기화하는 방식이다.

“CouchDB 하나 올리면 끝이겠지” 했는데, 전혀 아니었다 💀


🔍 증상: 동기화가 안 되는 세 가지 상황

CouchDB 설정에서 예상치 못한 에러를 만난 순간

Obsidian에 Self-hosted LiveSync 플러그인을 설치하고 CouchDB 주소를 넣었다. “Test” 버튼을 누르는 순간 에러 3연타.

⚠️ 증상 1 — CORS 에러로 연결 자체 실패

플러그인 설정에서 CouchDB URL을 입력하고 테스트하면 바로 막힌다.

Access to fetch at 'https://obs.example.com/' from origin 'app://obsidian.md'
has been blocked by CORS policy

Obsidian 데스크톱 앱은 app://obsidian.md라는 독자적인 origin을 사용한다. CouchDB는 기본적으로 CORS를 전면 차단한다. Nginx에서 CORS 헤더를 추가해봤자 CouchDB 레벨에서 막으면 소용없다.

⚠️ 증상 2 — 시스템 DB 누락으로 500 에러

CORS를 해결하고 나니 이번엔 500 에러다.

{"error":"not_found","reason":"Database does not exist."}

CouchDB를 Docker로 처음 올리면 _users_replicator 시스템 데이터베이스가 자동으로 생성되지 않는다. LiveSync 플러그인이 내부적으로 이 DB에 접근하려 하면서 터진다.

⚠️ 증상 3 — 큰 파일 동기화 실패

연결은 됐는데, 일부 노트가 동기화되지 않는다. Obsidian 플러그인 로그를 보면:

document_too_large: {"error":"document_too_large","reason":"..."}

CouchDB의 기본 max_document_size8MB다. Obsidian 노트에 이미지를 인라인으로 넣거나 첨부 파일이 조금만 커도 이 제한에 걸린다. LiveSync는 바이너리 파일도 CouchDB 도큐먼트로 저장하기 때문이다.

Base64 인코딩까지 고려하면 실제 용량의 약 1.33배가 도큐먼트 크기가 된다. 결국 6MB 파일이면 이미 8MB 제한을 초과한다.


🧠 원인: CouchDB 기본값은 Obsidian용이 아니다

원인을 파악하고 나서의 표정

CouchDB는 범용 NoSQL DB다. Obsidian LiveSync의 요구사항은 CouchDB 기본 설정과 여러 군데 충돌한다.

원인 1 — CORS 허용 origin 미설정

CouchDB [httpd] 섹션의 enable_cors가 기본값 false다. Obsidian 앱은 플랫폼마다 다른 origin을 사용한다.

플랫폼Origin
데스크톱 (Electron)app://obsidian.md
모바일 (Capacitor)capacitor://localhost
웹 뷰어http://localhost

세 개 전부 등록해야 어디서든 동기화된다.

핵심: CORS는 CouchDB 공식 문서에도 명시된 필수 설정이다. Nginx 레벨이 아닌 CouchDB 자체에서 설정해야 한다.

원인 2 — 시스템 DB 수동 생성 필요

CouchDB 3.x의 Docker 이미지는 “single node setup”을 자동으로 완료하지 않는다. /_cluster_setup을 호출하거나, _users_replicator를 직접 만들어야 한다.

CouchDB Docker 공식 문서에도 있는 내용이지만, Docker Compose로 한 줄 올리고 바로 쓰려는 사람은 놓치기 쉽다.

원인 3 — max_document_size 기본값이 너무 작다

LiveSync는 노트 본문뿐 아니라 첨부 파일(이미지, PDF 등)도 CouchDB 도큐먼트로 저장한다. 6MB 이상 파일은 인코딩 후 8MB 제한을 넘어서 전부 실패한다.

주의: 이미지 하나 없는 텍스트 볼트라면 기본값으로도 버틸 수 있다. 그러나 첨부 파일이 있는 순간 무조건 터진다.


🛠️ 해결: Docker Compose + CouchDB 설정 + Nginx

하나씩 해결해 나가는 과정

✅ Step 1 — Docker Compose에 CouchDB 추가

기존 스택에 CouchDB 컨테이너를 추가했다.

# docker-compose.yml (발췌)
couchdb:
  image: couchdb:3.5.1
  restart: unless-stopped
  environment:
    - COUCHDB_USER=obsidian
    - COUCHDB_PASSWORD=${COUCHDB_PASSWORD}
  volumes:
    - couchdb-data:/opt/couchdb/data
  networks:
    - wp-net

핵심: ARM 서버에서도 couchdb:3.5.1 공식 이미지가 linux/arm64를 지원한다. 별도 빌드가 필요 없다.

✅ Step 2 — 시스템 DB 생성

컨테이너가 올라온 직후, 시스템 DB를 수동으로 생성한다.

# CouchDB 시스템 DB 초기화
curl -X PUT http://obsidian:${COUCHDB_PASSWORD}@localhost:5984/_users
curl -X PUT http://obsidian:${COUCHDB_PASSWORD}@localhost:5984/_replicator

# Obsidian 볼트용 DB 생성
curl -X PUT http://obsidian:${COUCHDB_PASSWORD}@localhost:5984/obsidian-vault

이걸 빠뜨리면 LiveSync 플러그인이 500 에러를 뱉는다. Docker entrypoint 스크립트에 넣어두면 재배포할 때도 안전하게 넘어간다.

✅ Step 3 — CORS 설정

CouchDB REST API로 직접 설정하는 게 가장 확실하다.

# CORS 활성화
curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/httpd/enable_cors \
  -d '"true"'

# 허용 origin 등록 (3개 전부 필요)
curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/cors/origins \
  -d '"app://obsidian.md,capacitor://localhost,http://localhost"'

# 허용 메서드
curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/cors/methods \
  -d '"GET, PUT, POST, HEAD, DELETE"'

# 허용 헤더
curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/cors/headers \
  -d '"accept, authorization, content-type, origin, referer"'

# 크리덴셜 허용
curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/cors/credentials \
  -d '"true"'

팁: Nginx 레벨에서 CORS 헤더를 따로 추가하면 안 된다. Access-Control-Allow-Origin 헤더가 두 번 나와서 오히려 브라우저가 거부한다. CouchDB 자체 설정으로만 처리하는 게 맞다.

✅ Step 4 — max_document_size 확장

curl -X PUT http://obsidian:${PW}@localhost:5984/_node/_local/_config/couchdb/max_document_size \
  -d '"50000000"'

50MB로 설정했다. Obsidian 볼트에 50MB짜리 파일을 넣을 일은 거의 없지만, 여유 있게 잡아두는 게 낫다. 이 설정을 빠뜨리면 6MB 이상 이미지가 하나씩 조용히 누락된다 — 에러 메시지도 모호해서 원인 찾는 데 시간을 날린다.

✅ Step 5 — Nginx 리버스 프록시

server {
    listen 443 ssl;
    server_name obs.example.com;

    ssl_certificate     /etc/nginx/certs/origin.pem;
    ssl_certificate_key /etc/nginx/certs/origin.key;

    location / {
        proxy_pass http://couchdb:5984;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # CORS 헤더는 CouchDB가 직접 내보냄 — 여기서 add_header 금지
    }
}

주의: add_header Access-Control-Allow-Origin을 Nginx에 넣지 않는다. CouchDB가 직접 CORS 헤더를 붙이기 때문에 Nginx에서 중복 추가하면 브라우저가 즉시 거부한다.

Cloudflare에 A 레코드를 추가하고 SSL 모드는 “Full (Strict)“으로 설정했다. 이미 와일드카드 Origin Certificate가 있으면 인증서 추가 발급은 불필요하다.

팁: Cloudflare SSL 설정이 궁금하다면 이전 글 Cloudflare Full Strict SSL + Nginx 리버스 프록시 삽질 총정리에서 자세히 다뤘다.

✅ Step 6 — Obsidian 플러그인 설정

Self-hosted LiveSync 플러그인(v0.25.50)을 설치하고 아래 값을 입력한다.

설정
CouchDB URLhttps://obs.example.com
Usernameobsidian
Password(Keychain에서 조회)
Database nameobsidian-vault
동기화 모드LiveSync (실시간)

첫 번째 기기(Windows)에서 “서버 초기화(Rebuild everything)“를 실행해서 볼트를 서버에 업로드한다. 두 번째 기기(Mac Mini)에서는 “Fetch from Remote”로 볼트를 가져온다.

핵심: 두 번째 기기에서 절대로 “서버 초기화”를 누르면 안 된다. 서버에 올라간 데이터를 전부 날려버린다.


🛡️ 예방: 재발 방지 체크리스트

CouchDB를 Docker로 올릴 때마다 확인할 항목들이다.

배포 전 체크리스트

  • _users, _replicator 시스템 DB 생성 확인
  • CORS enable_cors = true 확인
  • CORS originsapp://obsidian.md 포함 확인
  • max_document_size가 50MB 이상인지 확인
  • Nginx에서 CORS 헤더를 추가하지 않았는지 확인 (중복 방지)
  • Cloudflare SSL 모드: Full (Strict) 확인
  • CouchDB 비밀번호가 Keychain에 저장되었는지 확인

동기화 장애 시 디버그 순서

# 1. CouchDB 컨테이너 상태 확인
docker compose ps couchdb
docker compose logs couchdb --tail=20

# 2. CouchDB 직접 접근 테스트 (컨테이너 내부)
docker compose exec couchdb curl -s http://localhost:5984/

# 3. Nginx 프록시 테스트 (외부)
curl -s https://obs.example.com/

# 4. CORS 설정 확인
curl -s http://obsidian:PW@localhost:5984/_node/_local/_config/cors/origins

# 5. DB 존재 여부 확인
curl -s http://obsidian:PW@localhost:5984/_all_dbs

팁: Obsidian 플러그인의 “Test” 버튼이 실패하면, 먼저 curl로 CouchDB에 직접 접근해봐야 한다. 플러그인 에러 메시지는 근본 원인을 숨기는 경우가 많다.


📋 정리

문제를 다 해결하고 나니 뿌듯하다

상황안티패턴권장 패턴
CouchDB Docker 초기화docker compose up만 하고 끝시스템 DB(_users, _replicator) 수동 생성
CORS 설정Nginx에서 CORS 헤더 추가CouchDB 자체 CORS 설정 사용 (중복 헤더 방지)
큰 파일 동기화 실패기본 8MB 제한 방치max_document_size 50MB로 확장
두 번째 기기 연결”서버 초기화” 실행”Fetch from Remote”로 기존 데이터 수신
비밀번호 관리.env에만 저장Keychain + secrets-registry 이중 기록

CouchDB + Obsidian LiveSync는 한번 세팅하면 정말 편하다. iCloud/Google Drive 동기화의 충돌 지옥에서 벗어나서, 실시간으로 양방향 동기화가 된다.

초기 설정에서 빠뜨리기 쉬운 항목이 딱 세 개다. 시스템 DB, CORS, document size — 하나라도 놓치면 플러그인이 모호한 에러만 보여준다.

체크리스트를 한 번만 따라가면, 그다음부턴 신경 쓸 일이 없다 ✨


함께 읽으면 좋은 글