Ghost 셀프호스팅 구축기 - n8n 경험을 바탕으로 한 두 번째 도전

Ghost Blog

n8n 셀프호스팅을 성공한 후, 개인 블로그도 직접 운영해보고 싶어졌습니다. n8n 경험이 있어도 이번엔 수월하지 않을 줄 알았는데, 역시나 새로운 문제들이 기다리고 있었습니다.

Ghost 셀프호스팅을 시작하게 된 배경

n8n 성공 후의 자신감(?)

  • n8n 셀프호스팅 완료
  • 블로그 필요성: 개인 브랜드와 콘텐츠 관리를 위한 독립적인 플랫폼 필요
  • 기술적 호기심
  • 결과: 생각보다 쉽지 않았음 😅

Ghost를 선택한 이유

Ghost의 장점:
- 깔끔한 마크다운 에디터
- SEO 최적화가 잘 되어 있음  
- 개발자 친화적인 구조
- 셀프호스팅 가능

환경 설정 및 목표

기존 인프라 현황

  • OS: Windows 11 + Docker Desktop
  • 네트워크: Cloudflare Tunnel (n8n에서 이미 사용 중)
  • 도메인: imiwork.com (기존 도메인 활용)

구축 목표

  • 서브도메인: blog.imiwork.com으로 Ghost 블로그 운영
  • 동시 운영: 기존 n8n 서비스와 함께 안정적 운영
  • 관리 편의성: 하나의 Docker Compose로 통합 관리
Before: [Cloudflare Tunnel] → [n8n만]
After:  [Cloudflare Tunnel] → [n8n + Ghost]

n8n과 Ghost 동시 운영을 위한 인프라 구성도

첫 번째 시도: 기본 설치의 함정

기본적인 접근

n8n처럼 간단할 줄 알고 기본 설정으로 시작했습니다.

services:
  ghost:
    image: ghost:latest
    ports:
      - "2368:2368"

첫 번째 벽: 컨테이너 재시작 루프

# 컨테이너 상태 확인
docker ps
# STATUS: Restarting (1) 3 seconds ago

# 로그 확인으로 원인 파악
docker logs ghost-blog
Error: connect ECONNREFUSED 127.0.0.1:3306

문제 발견: Ghost는 MySQL 데이터베이스가 기본 설정으로 되어있는데, 이를 설정하지 않아서 발생한 오류였습니다.

n8n vs Ghost의 차이점:
- n8n: 자체적으로 데이터 저장 (별도 DB 불필요)
- Ghost: 반드시 데이터베이스 필요 (MySQL, SQLite 등)

데이터베이스 설정: SQLite로 해결

개인 블로그에는 SQLite가 적합

MySQL 서버를 별도로 구축하기보다는, 개인 블로그 용도로는 SQLite가 충분하다고 판단했습니다.

environment:
  database__client: sqlite3
  database__connection__filename: /var/lib/ghost/content/data/ghost.db
  database__useNullAsDefault: 'true'

결과: 컨테이너가 정상 실행되기 시작했습니다! 🎉

SQLite 선택 이유

MySQL vs SQLite 비교:
- MySQL: 고성능, 복잡한 설정, 별도 컨테이너 필요
- SQLite: 설정 간단, 파일 기반, 개인 블로그에 충분한 성능

개인 블로그 특성상 SQLite로 충분하다고 판단

네트워크 접속 문제 해결

localhost 접속 실패

컨테이너는 실행되는데 localhost:2368로 접속할 수 없는 문제가 발생했습니다.

원인 분석: IPv6 바인딩 문제

컨테이너 내부 설정을 확인해보니:

{
  "server": {
    "host": "::"  // IPv6만 바인딩되어 있음
  }
}

해결: 설정 파일 직접 수정

{
  "url": "https://blog.imiwork.com",
  "server": {
    "host": "0.0.0.0"  // 모든 인터페이스에서 접근 가능하게 변경
  }
}

Claude의 도움: "Ghost는 production 모드에서 엄격한 설정을 요구해서 host 설정을 명시적으로 해줘야 해요."

최대 난관: 메일 설정

Ghost의 엄격한 요구사항

Ghost 관리자 계정을 생성하려면 이메일 인증이 필수입니다. 이 부분에서 가장 오랜 시간을 소모했습니다.

시도한 방법들

1. 메일 비활성화 시도

나: "메일 설정 없이 관리자 계정만 만들 수 없나요?"
Claude: "Ghost는 보안상 이메일 인증을 우회할 수 없어요."

결과: 불가능

2. 가짜 메일 서버 시도

mail__transport: stub

결과: 여전히 실패

3. Gmail SMTP 연동 (최종 해결)

실제 이메일 서비스와 연동하는 것이 유일한 해결책이었습니다.

Gmail SMTP 설정 과정

Gmail 앱 비밀번호 생성

  1. Google 계정 보안 설정 페이지 접속
  2. 2단계 인증 활성화 (필수 조건)
  3. 앱 비밀번호 생성
  4. 16자리 코드 생성 및 복사

Docker Compose 설정 추가

environment:
  mail__transport: SMTP
  mail__options__service: Gmail
  mail__options__auth__user: [email protected]
  mail__options__auth__pass: [16자리 앱 비밀번호]
  mail__from: [email protected]

주의사항: 일반 Gmail 비밀번호가 아닌 앱 전용 비밀번호를 사용해야 합니다.

Cloudflare Tunnel 확장

기존 n8n 터널에 Ghost 추가

기존 n8n 터널 설정을 확장해서 Ghost도 함께 관리하기로 했습니다.

# ~/.cloudflared/config.yml
tunnel: n8n-tunnel
ingress:
  - hostname: n8n.imiwork.com
    service: http://localhost:5678
  - hostname: blog.imiwork.com  # Ghost 추가
    service: http://localhost:2368
  - service: http_status:404

DNS 레코드 추가

Cloudflare 대시보드에서 새로운 DNS 레코드를 추가해야 했습니다:

Type: CNAME
Name: blog
Target: [Cloudflare 터널 주소]

Claude의 조언: "하나의 터널로 여러 서비스를 관리할 수 있어서 효율적이에요."

최종 통합 설정

완성된 docker-compose.yml

version: '3.8'

services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n-docker
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_data:/home/node/.n8n
    environment:
      - GENERIC_TIMEZONE=Asia/Seoul

  ghost:
    image: ghost:latest
    container_name: ghost-blog
    restart: always
    ports:
      - "127.0.0.1:2368:2368"
    volumes:
      - ghost_content:/var/lib/ghost/content
    environment:
      url: https://blog.imiwork.com
      database__client: sqlite3
      database__connection__filename: /var/lib/ghost/content/data/ghost.db
      database__useNullAsDefault: 'true'
      TZ: Asia/Seoul
      mail__transport: SMTP
      mail__options__service: Gmail
      mail__options__auth__user: [email protected]
      mail__options__auth__pass: [앱 비밀번호]
      mail__from: [email protected]

volumes:
  n8n_data:
  ghost_content:

n8n vs Ghost: 셀프호스팅 비교

설치 난이도 비교

n8n (첫 번째 경험):

  • 기술적 어려움: Docker, CLI 모든 게 낯섦
  • 설정 복잡도: 상대적으로 단순 (메일 설정 불필요)
  • 심리적 부담: 모든 게 처음이라 높음

Ghost (두 번째 경험):

  • 기술적 어려움: Docker, CLI 어느 정도 익숙
  • 설정 복잡도: 메일 설정 등 더 복잡한 요구사항
  • 심리적 부담: 경험이 있어서 상대적으로 낮음

실제 느낀 점

예상: "Docker 알고 있으니까 Ghost는 쉬울 거야"
현실: "각 서비스마다 고유한 복잡성이 있구나"

교훈: 기술 스택은 비슷해도 각 애플리케이션마다 
      고유한 요구사항과 함정이 있다

운영 관점에서의 차이

안정성:

  • n8n: 워크플로우만 잘 설정하면 거의 손댈 일 없음
  • Ghost: 메일 서버 상태에 의존적 (Gmail 계정 문제 시 영향)

확장성:

  • n8n: 워크플로우 추가만 하면 됨
  • Ghost: 플러그인, 테마 등 확장 요소 많음

셀프호스팅 초보자를 위한 교훈

💡 각 서비스마다 고유한 복잡성이 있다

Docker 경험이 있어도 새로운 서비스는 새로운 도전입니다. 과신하지 말고 차근차근 접근하세요.

💡 공식 문서를 꼼꼼히 읽어보기

Ghost 공식 문서에 메일 설정 필수라고 명시되어 있었는데, 처음에 대충 넘어갔다가 시간을 많이 허비했습니다.

💡 AI 어시스턴트 적극 활용

  • "Ghost에서 SQLite 사용하는 방법"
  • "Gmail SMTP 설정 방법"
  • "Docker 컨테이너가 재시작하는 이유"

💡 통합 관리의 장점

docker-compose로 여러 서비스를 통합 관리하니 훨씬 편리했습니다.

Ghost 셀프호스팅 설치 완료 후 관리자 대시보드

마무리: 성공적인 두 번째 도전

"첫 번째가 어렵다고 두 번째도 쉬운 건 아니지만, 경험이 있으면 문제 해결 과정이 체계적이 된다"

Ghost 셀프호스팅의 핵심은 메일 설정입니다. 이 부분만 제대로 해결하면 나머지는 크게 어렵지 않습니다.

SQLite를 사용하면 데이터베이스 관리 부담도 줄일 수 있고, Cloudflare Tunnel로 HTTPS와 CDN 혜택까지 받을 수 있어서 개인 블로그 용도로는 매우 만족스러운 결과입니다.


Subscribe to SENSE & AI

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
[email protected]
Subscribe