이 글의 목차
이 블로그는 한때 Jekyll로 글과 페이지를 만들고 GitHub Pages에 공개했다. 처음에는 간단했다. main에 변경을 넣고 사이트를 빌드해 올리면 됐다.
문제는 글이나 레이아웃을 자주 고치기 시작하면서 생겼다. PR을 열 때마다 사이트가 깨지지 않았는지 확인하고 싶었다. 그렇다고 기능 브랜치에서 만든 빌드를 실제 사이트에 바로 배포하고 싶지는 않았다. 검증은 필요하지만, 모든 변경이 곧바로 공개되어서는 안 된다.
내가 원한 동작은 단순했다. 기능 브랜치와 PR에서는 빌드와 검증만 한다. main에 병합된 변경만 실제 사이트에 배포한다.
원하는 동작: 검증은 자주, 배포는 신중하게
역할을 이렇게 나누면 된다.
- 기능 브랜치와 PR: Jekyll이 사이트를 만들 수 있는지, 언어별 경로와 링크가 맞는지 확인한다. 배포하지 않는다.
main에 병합된 변경: 프로덕션용 사이트를 다시 만들고 GitHub Pages가 읽는 브랜치에 게시한다.
PR의 초록색 체크는 “이 변경을 배포해도 된다”가 아니라 “이 변경은 빌드와 검증을 통과했다”는 뜻이다. 실제 공개는 병합이라는 별도 결정 뒤에 온다.
먼저 main과 gh-pages를 구분하자
main에는 사이트의 원본이 있다. Markdown 글, Jekyll 설정, 테마와 스크립트, 워크플로 코드가 여기에 들어간다. 사람은 주로 이 브랜치에서 원본을 고친다.
gh-pages에는 Jekyll이 원본에서 생성한 정적 결과물이 있다. 브라우저가 받는 HTML, CSS, 이미지와 그 밖의 공개 파일이다. GitHub Pages는 이 브랜치를 사이트로 제공한다.
즉, 두 브랜치는 같은 파일의 서로 다른 복사본이 아니다. main은 무엇을 만들지 설명하고, gh-pages는 그 결과를 담는다. 배포가 끝나면 gh-pages는 다시 생성할 수 있는 산출물이다. 그 내용을 직접 고치면 다음 빌드가 변경을 덮어쓴다.
흐름을 먼저 보면
기능 브랜치 / PR (검증만)
- push 또는 PR 열기·갱신모든 브랜치에서 검증
- Validate blogJekyll 빌드 + 언어·경로 검사
- 검증 결과gh-pages에 게시하지 않음
main에 병합 (게시 경계)
- 자동 빌드프로덕션 Jekyll 빌드 + 경로 검사
- 생성 파일gh-pages에 게시
- GitHub Pages생성된 사이트 제공
왜 CI와 CD를 분리했을까
CI는 “이 변경이 제대로 빌드되는가?”에 답한다. 여러 브랜치와 PR에서 실행해도 안전하다. 결과는 체크와 보고서이며, 실제 사이트는 바뀌지 않는다.
CD는 “검증한 결과로 지금 프로덕션을 교체할 것인가?”에 답한다. 배포 조건은 더 좁아야 한다. 이 구성에서는 main에 들어온 사이트 변경만 공개 작업을 시작했다.
생각해보자. PR마다 빌드 결과까지 공개하면 검증과 배포가 다시 한 동작이 된다. 브랜치마다 공개할 이유는 없다. 그래서 검증은 모든 브랜치에서 하고, 배포는 main에 병합된 변경으로 제한했다.
당시의 흐름
이제 실제 워크플로 이름을 보자. 두 작업은 서로 다른 입력과 결과를 가졌다.
- 어느 브랜치에 push하거나 PR을 열고 갱신:
Validate blog(.github/workflows/main.yml)가 실행되어 빌드와 언어별 경로를 검사한다. 게시하지 않는다. - Actions에서
Validate blog수동 실행: 같은 검증을 다시 실행한다. 게시하지 않는다. - 사이트에 영향을 주는 변경을
main에 push하거나 병합:Automatic build(.github/workflows/pages-deploy.yml)가 프로덕션을 빌드해gh-pages에 게시한다. - Actions에서
Automatic build수동 실행:main을 선택해야 작업 조건을 통과한다. 다른 브랜치에서는 배포하지 않는다.
main에 push하면 Validate blog도 실행된다. 반면 Automatic build는 .gitignore, README.md, LICENSE, ARTICLE_PROGRESS.md만 바뀐 커밋은 무시했다. 이런 변경은 검증은 받아도 배포를 시작하지 않았다.
Validate blog: 빌드하고 확인만 하기
검증 워크플로는 원본을 체크아웃하고 Ruby 2.7과 잠금 파일에 맞는 의존성을 준비한 뒤 다음 명령을 실행했다.
bundle exec jekyll build
ruby tools/verify_language_routes.rb _site
첫 명령은 Markdown과 설정으로 사이트를 만든다. 두 번째는 언어별 페이지와 글 경로, canonical URL, 번역 선택기의 양방향 링크, 렌더링된 제목, 이전 경로 redirect를 확인한다.
여기에는 gh-pages에 push하는 단계가 없다. 검증에 실패하면 병합 전에 고칠 수 있고, 통과하면 PR을 검토할 근거가 생긴다. 초록색 체크만으로 공개하지 않는 이유다.
Automatic build: main에서 프로덕션 게시하기
당시 프로덕션 워크플로도 Ruby 2.7과 Bundler를 준비했다. 전체 Git 이력을 가져온 뒤 bash tools/deploy.sh를 실행했다. 스크립트는 프로덕션 모드로 Jekyll을 빌드하고 언어 경로를 확인한 다음, 생성 결과로 gh-pages의 내용을 교체해 push했다. 당시 스크립트는 이 생성 브랜치를 force-push했다.
수동 실행에도 제한이 있었다. workflow_dispatch를 눌러도 선택한 ref가 main이 아니면 배포 작업이 실행되지 않았다. 이 가드는 실수로 기능 브랜치를 프로덕션에 올리지 않도록 했다.
이 과정에서 main의 원본은 그대로 남고, gh-pages만 새 빌드 결과로 바뀐다. gh-pages를 직접 고치지 않는 이유도 같다. 다음 배포가 원본에서 결과물을 다시 만들기 때문이다.
한 문장 수정으로 전체 흐름 따라가기
한국어 글에서 문장 하나를 고친다고 해보자.
main에서 기능 브랜치를 만든다._translations/ko/아래 원본 Markdown에서 문장을 고친 뒤 브랜치에 push한다.Validate blog가 Jekyll 빌드와 언어·경로 검사를 한다. 여기서는 사이트가 바뀌지 않는다.main을 대상으로 PR을 열고, 검증 결과와 변경 내용을 검토한다.- PR을 병합한다. 사이트에 영향을 주는 변경이면
Automatic build가 프로덕션을 다시 만든다. - 생성된 파일이
gh-pages에 게시되고 GitHub Pages가 새 페이지를 제공한다.
한 문장 수정이 공개되기까지 검증과 승인이 사이에 있다. 이 경계가 CI와 CD를 나눈 이유다.
당시 Jekyll 사이트를 로컬에서 미리보기
일반적인 Jekyll 미리보기에는 다음 명령을 썼다.
bundle install
bundle exec jekyll serve
프로덕션 빌드에서만 나타나는 문제는 당시 다음 명령으로 확인했다.
JEKYLL_ENV=production bundle exec jekyll build
이 글은 당시 환경을 설명한다. 지금 사이트를 Jekyll로 배포하는 절차는 아니다.
이후 바뀐 점
2026-10-06 현재 이 사이트의 프로덕션은 Jekyll/Chirpy에서 Astro 정적 사이트로 이전했다. 이 글은 이전 Jekyll 배포 구조를 기록한다.
지금의 공개 흐름은 main의 변경을 Node/Astro로 빌드하고, site-v2/dist를 자동으로 gh-pages에 게시한 뒤 GitHub Pages가 제공하는 방식이다. 구현은 달라졌지만 원칙은 같다. 변경을 먼저 검증하고, 검증된 결과만 프로덕션에 공개한다.