블로그 소스를 감추면서 GitHub Pages는 무료로 유지하기

2026-07-20

이 블로그는 소스가 공개돼 있었다. github.io 레포에 astro 소스가 그대로 들어 있어 누구나 src/를 열어 볼 수 있었다. 정적 블로그는 어차피 빌드된 HTML을 모두에게 서빙하니 코드가 공개돼도 큰일은 아니다. 그런데 신경 쓰이는 것이 둘 있었다. 미발행 초안과 커밋 히스토리다. published: false로 묻어 둔 글과 .hide/ 폴더에 제목만 잡아 둔 글이 전부 public 레포에 남아 있었다.

그래서 소스는 private로 감추고, 사이트는 지금처럼 public으로 무료 유지하고 싶었다. 문제는 이 둘이 GitHub Pages에서 그냥은 양립하지 않는다는 점이다.

이 글은 그 둘을 쪼개 붙인 기록이며, 다루는 내용은 다음과 같다.

감추면 실익이 있는 것, 이미 공개된 것

먼저 계산부터 하고 넘어가는 게 좋다. 정적 사이트에서 “소스를 감춘다”의 실익은 생각보다 좁다. 빌드된 HTML·CSS·JS는 어차피 모든 방문자에게 그대로 내려간다. private로 실제로 가려지는 것은 다음 정도다.

내 경우 실질적인 동기는 초안이었다. 아이디어만 적어 둔 글이 public 히스토리에 남는 게 싫었다. 코드 자체를 숨기는 건 부차적이었고, 어차피 빌드 결과물로 대부분 드러난다. 이 계산을 먼저 해 두면 무엇을 위해 복잡도를 늘리는지가 분명해진다.

private 레포에서 Pages를 못 서빙한다는 제약

가장 단순한 해법은 레포를 private로 바꾸고 Pages를 그대로 쓰는 것이다. 그런데 이게 막힌다. GitHub Pages를 private 레포에서 서빙하려면 GitHub Pro나 Advanced Security가 필요하다. 무료 플랜에서 Pages는 public 레포에서만 동작한다.

그래서 방향을 뒤집었다. 소스는 private, 서빙은 public — 두 레포로 나누되 Pages는 계속 public 레포가 담당하게 하는 것이다. 이러면 유료 요소 없이 무료 플랜을 유지한다.

두 레포로 나눈 구조

[private: seotory/blog]                        [public: seotory.github.io]
  astro 소스 + 히스토리         CI 빌드           빌드 결과물(dist)만
  └─ .github/workflows/deploy.yml ───push───▶   └─ index.html, _astro/, CNAME ...
                                                       │ Pages가 서빙

                                                  blog.seotory.com

즉 public 레포는 소스의 사본이 아니라 배포 산출물의 저장소가 된다.

크로스 레포 배포의 조각들

private CI가 다른 레포로 push하는 건 기본 설정만으로는 안 된다. 필요한 조각이 둘 있다.

배포 키가 필요한 이유

워크플로가 기본으로 받는 GITHUB_TOKEN자기 레포에만 권한이 있다. 다른 레포로 push하려면 별도 자격증명이 필요하고, SSH 배포 키를 쓰면 깔끔하다.

배포 워크플로

peaceiris/actions-gh-pages가 이 크로스 레포 push를 지원한다.

- uses: peaceiris/actions-gh-pages@v4
  with:
    deploy_key: ${{ secrets.DEPLOY_KEY }}
    external_repository: seotory/seotory.github.io
    publish_branch: master
    publish_dir: ./dist
    force_orphan: true

external_repository가 대상 레포, deploy_key가 위에서 만든 개인키다. force_orphan: truepush할 때마다 대상 브랜치를 단일 커밋으로 덮어쓴다. 덕분에 public 레포에 빌드 결과물이 쌓이지 않고 소스도 절대 남지 않는다. 처음 돌릴 때는 이 옵션이 기존 public 레포의 소스 히스토리까지 한 번에 갈아엎어 준다.

세 가지 함정

브랜치 서빙에서 Jekyll이 삼키는 _astro 폴더

Pages 소스를 “Deploy from a branch”로 바꾸면 Pages는 그 브랜치를 Jekyll로 한 번 처리한다. 그런데 Jekyll은 밑줄(_)로 시작하는 폴더를 빌드에서 제외한다. astro의 정적 자산은 _astro/에 들어 있으므로, 그대로 두면 CSS·JS가 전부 404가 된다.

해결은 출력에 빈 .nojekyll 파일을 하나 넣는 것이다. 이 파일이 있으면 Pages가 Jekyll 처리를 건너뛰고 파일을 그대로 서빙한다. peaceiris/actions-gh-pages는 이 파일을 자동으로 넣어 주지만, 손으로 배포한다면 직접 넣어야 한다.

private 레포에서 매번 실패하는 CodeQL

public 레포에서 쓰던 CodeQL 워크플로를 그대로 들고 오면 private 레포에서 매번 실패한다. 분석은 되는데 마지막 업로드에서 “code scanning is not enabled”로 막힌다. 코드 스캐닝은 public 레포에선 무료지만, private 레포에선 Advanced Security가 있어야 켜진다.

배포와는 무관한 실패지만 빨간 표시가 매 커밋에 붙는다. 정적 블로그에 코드 스캐닝은 실익이 없으니 워크플로를 지웠다.

force_orphan이 회수하지 못하는 과거

솔직한 한계 하나. force_orphan앞으로의 소스와 초안을 확실히 감춘다. 그런데 이미 public이던 시절의 코드는 완전히 회수되지 않는다. 누군가 이미 clone·fork했거나, 강제 푸시 뒤에도 한동안 SHA로 접근 가능하거나, 검색엔진·아카이브에 캐시된 것까지는 못 지운다. 흔적까지 지우려면 public 레포를 삭제하고 새로 만드는 수밖에 없다. 개인 블로그라 대개 force_orphan으로 충분하지만, 이건 알고 선택해야 한다.

정리

최종 구조를 요약하면 이렇다.

private (seotory/blog) public (seotory.github.io)
내용 astro 소스 + 히스토리 + CI 빌드 결과물만, 단일 커밋
역할 작업·빌드 Pages 서빙 (무료)
자격증명 DEPLOY_KEY (개인키) Deploy key (공개키, 쓰기)

이번 분리에서 남는 것은 다음과 같다.

소스를 감추는 일은 생각보다 확실하지 않고, 대신 초안을 안전하게 쌓아 둘 공간을 얻는 게 이 구조의 실익이다. 그 목적이 분명하다면 무료 플랜에서도 충분히 만들 수 있다.

Tags :github-pagesastroci

같은 카테고리의 다른 글

zsh, zplug과 oh-my-zsh 시작하기

최종 모습 zsh을 사용하게 된 계기 bash sh을 아주 잘쓰고 있었음 실수로 rm -rf 를 입력 혼돈의 카오스 닷으로 시작하는 설정 파일들이 날라감 (.bash 등등) 이렇게 된 이상 그 좋다던...

더보기