Obsidian + GitLab + GitHub로 블로그 자동 배포 파이프라인 구축하기

Obsidian에서 글을 쓰면 GitLab에 백업되고, 자동으로 GitHub Pages 블로그에도 배포되는 완전 자동화 파이프라인을 구축했다.


🎯 목표

  • Obsidian: 마크다운 에디터로 블로그 글 작성
  • GitLab: 전체 Obsidian Vault 백업 (Private)
  • GitHub: 블로그 콘텐츠만 배포 (Public)
  • 완전 자동화: 글 작성 후 손 안 대고 자동 배포

📋 기존 문제점

처음에는 다음과 같은 구조를 사용하고 있었다:

Obsidian Vault
└── content/          # 블로그 콘텐츠
    └── 글들.md

↓ Obsidian Git 플러그인

GitLab (Private)      # 전체 Vault 백업
└── Obsidian Vault/

↓ GitHub Publisher 플러그인

GitHub (Public)       # 블로그 배포
└── content/

문제점

  1. 파일 삭제가 동기화 안 됨

    • Obsidian에서 파일을 이동/삭제해도 GitHub에는 남아있음
    • GitHub Publisher 플러그인이 추가/수정만 하고 삭제는 처리 안 함
  2. 수동 작업 필요

    • 글 수정 → GitLab 백업 → GitHub Publisher 수동 실행
    • 완전 자동화가 아님
  3. 두 저장소 불일치

    • GitLab과 GitHub의 content 폴더가 달라질 수 있음

💡 해결책: GitLab CI/CD

GitLab CI/CD를 활용해서 GitLab에 push되면 자동으로 GitHub에도 동기화하는 파이프라인을 구축했다.

최종 아키텍처

Obsidian에서 글 작성
       ↓
GitLab에 자동 push (Obsidian Git 플러그인)
       ↓
GitLab CI/CD 자동 실행 🤖
       ↓
GitHub 블로그 repo에 자동 sync ✅
       ↓
Quartz 자동 빌드 (GitHub Actions)
       ↓
블로그 자동 배포 🚀

🔧 구현 과정

1. GitHub Personal Access Token 생성

GitLab CI/CD가 GitHub에 push하려면 인증이 필요하다.

  1. GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. Generate new token (classic)
  3. 토큰 설정:
    • Name: GitLab CI Sync
    • Expiration: Custom (1년 또는 원하는 기간)
    • Scopes: repo (전체 체크)
  4. Generate token
  5. 토큰 복사 (한 번만 보여줌!)

2. GitLab에 Token 등록

GitLab 프로젝트에서 Token을 환경변수로 등록한다.

  1. GitLab → Project → Settings → CI/CD
  2. Variables 섹션 펼치기
  3. Add variable:
    • Key: GITHUB_TOKEN
    • Value: (복사한 GitHub Token)
    • Type: Variable
    • ✅ Protect variable
    • ✅ Mask variable

이렇게 하면 CI/CD 파이프라인에서 ${GITHUB_TOKEN}으로 사용할 수 있다.

3. .gitlab-ci.yml 작성

Obsidian Vault 루트에 .gitlab-ci.yml 파일 생성:

# GitLab CI/CD: Sync content folder to GitHub
# Obsidian Vault (GitLab) → Blog Repo (GitHub)
 
stages:
  - sync
 
sync_to_github:
  stage: sync
  image: bitnami/git:latest
  
  # content 폴더가 변경된 경우에만 실행
  only:
    changes:
      - content/**/*
  
  # main 브랜치에 push될 때만 실행
  only:
    - main
  
  before_script:
    - git config --global user.name "GitLab CI"
    - git config --global user.email "ci@gitlab.com"
  
  script:
    - echo "🚀 Syncing content folder to GitHub..."
    
    # GitHub 블로그 repo clone
    - git clone https://${GITHUB_TOKEN}@github.com/username/blog-repo.git temp_blog
    
    # 기존 content 폴더 삭제 (완전 동기화)
    - rm -rf temp_blog/content
    
    # 새로운 content 폴더 복사
    - cp -r content temp_blog/content
    
    # GitHub에 commit & push
    - cd temp_blog
    - git add content/
    - |
      if git diff --staged --quiet; then
        echo "✅ No changes to sync"
      else
        git commit -m "🔄 Auto-sync from Obsidian (GitLab) - $(date '+%Y-%m-%d %H:%M:%S')"
        git push origin main
        echo "✅ Successfully synced to GitHub!"
      fi
  
  allow_failure: false

주요 포인트

only: changes: content/**/*

  • content 폴더가 변경된 경우에만 CI/CD 실행
  • 다른 파일(설정, 템플릿 등) 변경 시에는 실행 안 함
  • 효율적이고 불필요한 실행 방지

rm -rfcp -r

  • 기존 content 폴더를 완전히 삭제하고 새로 복사
  • 파일 삭제도 정확히 동기화됨
  • 양쪽 content 폴더가 항상 일치

if git diff --staged --quiet

  • 변경사항이 없으면 commit 안 함
  • 불필요한 commit 방지

4. GitLab Shared Runners 활성화

  1. GitLab → Settings → CI/CD → Runners
  2. Shared runners 활성화 확인
  3. 이미 활성화되어 있으면 OK

5. 테스트

  1. Obsidian에서 content 폴더에 글 작성/수정
  2. Obsidian Git 플러그인으로 GitLab에 push
  3. GitLab CI/CD 자동 실행
  4. GitHub repo 확인 → content 폴더 동기화 완료!

🐛 트러블슈팅

문제 1: Job이 Pending 상태로 멈춤

증상:

This job is stuck because of one of the following problems. 
There are no active runners online, no runners for the protected branch, 
or no runners that match all of the job's tags: docker

원인: .gitlab-ci.ymltags: docker가 있는데 해당 태그를 가진 Runner가 없음

해결:

  • .gitlab-ci.yml에서 tags: docker 라인 삭제
  • GitLab Shared Runners 사용 (별도 Runner 설치 불필요)

문제 2: “git: ‘sh’ is not a git command”

증상:

git: 'sh' is not a git command. See 'git --help'.
ERROR: Job failed: exit code 1

원인: alpine/git 이미지에 shell 명령어가 부족

해결: Docker 이미지를 alpine/git:latest에서 bitnami/git:latest로 변경


✅ 결과

Before: 수동 작업 많음

1. Obsidian에서 글 작성
2. Obsidian Git으로 GitLab push
3. GitHub Publisher 플러그인 수동 실행
4. 파일 삭제는 GitHub에서 수동 삭제
5. Quartz 빌드 대기

After: 완전 자동화

1. Obsidian에서 글 작성
   ↓ (끝!)
   
자동으로:
- GitLab 백업
- GitHub 동기화 (삭제 포함)
- 블로그 배포

💡 배운 점

1. GitLab CI/CD는 강력하다

  • GitHub Actions만 써봤는데 GitLab CI/CD도 사용법이 비슷하고 강력하다
  • Private repo에서도 무료로 월 400분 제공 (충분함)

2. only: changes로 효율적인 파이프라인

  • 모든 push마다 실행되는 게 아니라 특정 폴더 변경 시에만 실행
  • CI/CD 분수(quota) 절약

3. 완전 동기화는 rm + cp가 확실하다

  • rsync, git subtree 등 여러 방법 고려했지만
  • 단순하게 삭제 후 복사가 가장 확실하고 버그 없음

4. 자동화의 가치

  • 한 번 설정하면 영원히 편함
  • 글 쓰는 데만 집중 가능
  • 배포 과정을 신경 쓸 필요 없음

🔗 참고 자료


📌 다음 개선 사항

  • Slack/Discord 알림 추가 (배포 완료 시)
  • 배포 실패 시 자동 롤백
  • 배포 전 Markdown 유효성 검사
  • 이미지 최적화 자동화

결론: Obsidian + GitLab CI/CD + GitHub는 완벽한 조합이다. 한 번 설정하면 글쓰기에만 집중할 수 있는 환경이 완성된다. 🚀