로컬에서 Markdown을 쓰고 git push로 자동 배포하는 다국어 블로그 게시 흐름
이 글이 해결하려는 문제
개인 블로그가 멈추는 이유는 사이트를 만들 줄 몰라서가 아니라 글을 올리는 과정이 너무 무거워서인 경우가 많습니다. 글 한 편마다 설정을 손으로 고치고, 직접 build하고, 서버에 업로드해야 한다면 시간이 갈수록 콘텐츠 생산 자체가 끊깁니다.
그래서 저는 블로그 게시 흐름을 개발 작업과 최대한 비슷하게 바꿨습니다. 로컬에서 Markdown을 작성하고 GitHub에 push하면 GitHub Actions가 build와 배포를 맡습니다. 글쓰기는 콘텐츠 작업으로 남기고 운영 작업은 자동화로 분리하는 방식입니다.
이런 경우에 잘 맞는다
- 개인 블로그, 기술 콘텐츠 사이트, 가벼운 문서 사이트를 운영
- 글을 Markdown 파일로 repository에 보관하고 싶음
- WordPress 같은 무거운 관리자 시스템을 유지하고 싶지 않음
- 여러 언어의 콘텐츠를 장기간 같은 slug 구조로 관리하고 싶음
온라인 관리자 화면, 여러 사람의 편집 협업, 시각적인 승인 workflow가 핵심이라면 다른 CMS가 더 적합할 수 있습니다.
최종적으로 원하는 상태
현재 이 구조에서는 새 글 하나를 발행할 때 흐름을 다음 정도로 줄일 수 있습니다.
- 로컬에서 언어별 글 scaffold 생성
- Markdown 원고 작성과 localization
- 로컬 preview로 내용과 레이아웃 확인
git push origin main- GitHub Actions가 자동 build / deploy
즉 콘텐츠 생산과 배포를 분리합니다. 글을 쓰는 동안에는 콘텐츠에 집중하고, 반복적인 공개 과정은 자동화가 맡습니다.
환경
현재 블로그 stack은 다음과 같습니다.
- framework:
Astro - content layer:
astro:content - content format:
Markdown - deploy: static build artifact
- web server:
Nginx - automation:
GitHub Actions
지금은 언어별 디렉터리를 분리합니다.
src/content/posts/zh-cn/*.md
src/content/posts/en-us/*.md
src/content/posts/zh-tw/*.md
src/content/posts/ja-jp/*.md
src/content/posts/ko-kr/*.md
같은 글은 모든 언어에서 같은 path를 사용합니다. 예를 들어:
- 중국어 간체:
/posts/<path>/ - 영어:
/en/posts/<path>/ - 중국어 번체:
/zh-tw/posts/<path>/ - 일본어:
/ja/posts/<path>/ - 한국어:
/ko/posts/<path>/
실제 흐름
1. 먼저 콘텐츠 모델을 고정한다
처음부터 content model이 안정적이지 않으면 글을 하나 추가할 때마다 routing과 display logic을 다시 손보게 됩니다.
현재는 글, page, category, resource를 astro:content로 관리하고 핵심 식별자를 단순하게 유지합니다.
- article:
path - page / category:
key
새 콘텐츠를 추가할 때 Markdown 파일만 추가하면 되고 route mapping 코드를 매번 바꾸지 않습니다.
2. 글 scaffold 명령을 준비한다
매번 이전 글을 복사해서 새 파일을 만드는 방식은 금방 불편해집니다. 그래서 repository에 scaffold script를 둡니다.
npm run new:post -- "markdown-blog-publish-workflow" "本地写 Markdown,git push 后自动上线:我的双语博客发布流程" "Write Markdown Locally, Deploy on git push: My Bilingual Blog Publishing Workflow" writing
현재 scaffold는 같은 slug로 언어별 draft를 만들도록 확장돼 있습니다.
src/content/posts/zh-cn/markdown-blog-publish-workflow.md
src/content/posts/en-us/markdown-blog-publish-workflow.md
src/content/posts/zh-tw/markdown-blog-publish-workflow.md
src/content/posts/ja-jp/markdown-blog-publish-workflow.md
src/content/posts/ko-kr/markdown-blog-publish-workflow.md
기본값은:
draft: true
작성 도중의 글이 자동 배포되는 일을 막기 위해 중요합니다.
3. 로컬에서는 작성과 preview에 집중한다
글을 쓸 때는 Markdown을 수정하고 npm run dev로 확인합니다.
브라우저에서는 주로 다음을 봅니다.
- 홈에 새 글이 제대로 노출되는가
- 각 언어의 detail page가 같은 slug로 열리는가
- category / tags가 정상적으로 집계되는가
- title과 description이 검색 의도에 맞는가
- language switch로 같은 글의 다른 언어로 이동할 수 있는가
이 검증을 통과했다면 운영 서버에 SSH로 들어가 수동 배포할 이유는 없습니다.
4. 배포는 GitHub Actions에 맡긴다
전체 흐름에서 가장 중요한 부분입니다.
현재 deployment workflow는 대략 다음 순서입니다.
main으로 push- Actions에서
npm ci npm run builddist패키징- SSH로 서버에 업로드
- 새
releases/<timestamp>-<sha>로 압축 해제 currentsymlink 전환- 외부 URL 검증
이렇게 하면 글 게시도 일반적인 CI/CD 작업이 됩니다.
5. 운영 서버는 정적 파일만 제공한다
저는 운영 서버에서 build하지 않는 것을 원칙으로 잡았습니다. 서버는 이미 build된 산출물을 받아서 제공하는 역할만 합니다.
직접적인 장점은 다음과 같습니다.
- 서버 runtime이 단순해짐
- build 환경이 CI로 통일됨
- rollback이 쉬움
- 실패 지점을 build / upload / activate / public verify로 나눠 볼 수 있음
대략적인 운영 구조는 다음과 같습니다.
/var/www/aitokenhacks.com/releases/<release-id>
/var/www/aitokenhacks.com/current
매 배포마다 운영 디렉터리를 덮어쓰지 않고 새로운 release를 만든 뒤 symlink만 바꿉니다.
6. 자동 배포에는 진단 정보가 있어야 한다
workflow가 있다는 사실만으로 자동화가 완성되지는 않습니다. 실패했을 때 원인을 빠르게 설명할 수 있어야 실제 운영에서 도움이 됩니다.
그래서 deployment 단계에서 다음을 확인하도록 했습니다.
- 어떤 Deploy Secret이 빠졌는가
- SSH 연결이 가능한가
- remote directory가 존재하고 쓸 수 있는가
current가 최종적으로 어떤 release를 가리키는가- public verify가 통과했는가
이렇게 하면 빨간 실패 표시 하나만 보는 대신 GitHub Secrets, SSH, permission, release activation, public routing 중 어디에서 문제가 났는지 바로 좁힐 수 있습니다.
자주 묻는 문제
왜 온라인 CMS를 사용하지 않는가
현재 이 사이트에서 더 중요한 것은 editor UI보다 콘텐츠 자산, 검색 유입, 버전 관리입니다.
Markdown 파일을 Git으로 관리하면 다음 특성이 자연스럽게 생깁니다.
- 전체 history가 Git에 남음
- rollback 가능
- diff / review 가능
- 콘텐츠와 코드가 같은 repository 규칙을 따름
- 특정 CMS 관리자 화면의 가용성에 덜 의존
여러 언어를 왜 한 파일에 넣지 않는가
한 파일에 여러 frontmatter와 여러 본문을 섞기 시작하면 유지보수 비용이 빠르게 커집니다.
현재는 언어별 파일 + 동일한 path 방식을 사용합니다.
- Astro content collection과 잘 맞음
- 각 언어의 문장과 SEO metadata를 독립적으로 최적화 가능
- route와 hreflang을 slug 기준으로 연결하기 쉬움
자동 배포에서 가장 자주 실패하는 것은 무엇인가
실제로 자주 봤던 문제는 크게 두 종류였습니다.
- GitHub Actions 사용량이나 과금 문제로 job 자체가 시작되지 않음
- Deploy Secrets나 SSH 설정 오류로 원격 배포 단계에서 실패
그래서 우선 다음 순서로 봅니다.
- Actions job이 실제로 시작됐는가
- 필요한 Secrets가 모두 있는가
- SSH 사용자와 key가 유효한가
- remote directory permission이 배포를 허용하는가
마무리
개인 기술 블로그를 운영한다면 “글쓰기”와 “배포”를 분리하는 것을 권합니다.
글을 쓸 때는 내용 품질에 집중하고, 배포에서는 CI/CD가 반복 작업을 처리하게 합니다.
발행 과정이 “로컬에서 Markdown 작성 → git push” 정도로 줄어들면 블로그 업데이트가 특별한 운영 이벤트가 아니라 계속 반복할 수 있는 일상 작업이 됩니다.