·4분 분량

글만 쓰면 배포되는 블로그를 만들려다 네 번 막혔다

플랫폼 대신 Astro를 고른 건 자동화 때문이었다. 그런데 Cloudflare에 올리는 데서 계속 걸렸다.

AstroCloudflareGitHub Actions

개발 블로그를 다시 시작하기로 하고 어디에 쓸지부터 정해야 했다. velog와 티스토리가 먼저 떠올랐다. 글만 쓰면 되고 관리할 게 없다.

기준은 하나였다. 작업한 내용을 Claude가 읽고 초안을 쓰고, 발행까지 끝낼 수 있어야 한다.

이 기준으로 보면 플랫폼은 걸리는 데가 있었다.

플랫폼 대신 저장소를 골랐다

플랫폼에 쓰면 글이 그쪽 데이터베이스에 있다. 자동으로 올리려면 공개 API를 쓰거나, 결국 사람이 웹 에디터를 열고 붙여넣는 단계가 남는다. 초안을 아무리 빨리 만들어도 마지막이 수작업이면 거기서 막힌다.

글이 저장소 안의 마크다운 파일이면 그 단계가 없다. 파일을 쓰고, 커밋하고, push한다. 셋 다 명령어라 사람이 화면을 볼 이유가 없고, 그다음은 CI가 한다.

지금 이 글도 그렇게 올라간다. 작업 내용을 정리해달라고 하면 Claude가 저장소의 커밋 기록을 읽고 초안을 파일로 만든다. 내가 읽고 고친다. 고친 걸 push하면 배포된다.

Astro를 고른 이유

정적 사이트 생성기 중에 Astro를 골랐다. 세 가지가 결정적이었다.

글 파일 하나를 추가하는 게 유일한 반복 작업이다. 이번 글을 쓰면서 저장소를 다시 확인했는데, 글 목록·태그 페이지·카테고리 페이지 어디에도 글 제목이나 경로가 하드코딩된 곳이 없었다. 전부 빌드할 때 src/data/post/ 폴더를 훑어서 만든다. RSS와 sitemap, robots.txt의 sitemap 줄, 각 페이지의 canonical URL도 마찬가지다. 새 글을 올리면서 손대야 하는 다른 파일이 없다.

서버가 없다. output: 'static'이고 어댑터를 붙이지 않았다. 빌드 결과가 HTML 폴더 하나라서 배포가 파일 복사다. Cloudflare Pages 무료 구간에 그대로 올라간다.

frontmatter를 스키마로 검증할 수 있다. 처음 직접 만들었을 때는 description을 필수로 걸어서 빠뜨리면 빌드가 깨지게 했다. 자동으로 만든 글에 필드가 비어 있어도 배포 전에 걸린다.

다만 이건 지금 반만 남아 있다. AstroWind 테마를 가져오면서 스키마상 필수 필드는 title 하나가 됐다. excerptpublishDate가 없어도 빌드는 통과한다. 지금은 저장소의 CLAUDE.md에 “필수로 취급한다”고 적어두고 규칙으로 지킨다. 남이 만든 테마를 쓰면 이런 걸 내주게 된다.

대시보드가 GitHub 연동을 계속 거부했다

여기까지가 결정이었고, 실제로 올리는 건 다른 문제였다. 네 번 막혔다.

Cloudflare 대시보드에서 GitHub 저장소를 연결하려는데 **“Error connecting to git account”**만 반복해서 떴다. GitHub 쪽에서는 Cloudflare 앱이 정상 설치된 것으로 보였고, 저장소 접근 권한도 열려 있었다.

원인을 더 파는 대신 방향을 바꿨다. Cloudflare가 GitHub를 보러 오게 하는 대신, GitHub Actions가 빌드해서 Cloudflare에 밀어 넣도록 했다. API 토큰만 있으면 되고 대시보드 연동이 필요 없다. 결과적으로 이게 더 나았다 — 배포 과정이 저장소 안의 파일 하나에 다 적혀 있다.

npm 11이 wrangler를 반만 설치했다

npm ci는 성공하는데 wrangler가 동작하지 않았다.

npm 11부터 패키지의 install 스크립트가 기본으로 차단된다. wrangler는 workerd라는 런타임 바이너리를 install 스크립트에서 내려받는데, 그 단계가 건너뛰어지면서 패키지는 설치됐지만 실행에 필요한 것이 없는 상태가 됐다. 설치가 성공했다고 나오니 원인을 찾는 데 시간이 걸렸다.

package.json에 이렇게 허용했다.

"allowScripts": ["workerd"]

이름을 하나씩 적어야 한다. 전체를 열면 의존성 트리의 모든 패키지가 설치 중에 스크립트를 돌릴 수 있게 된다.

커스텀 도메인 때문에 Workers에서 Pages로 옮겼다

처음엔 Cloudflare Workers에 올렸다. 배포는 됐고 Cloudflare가 만들어준 무작위이름.workers.dev 주소로 접속도 됐다. 그런데 blog.study-about.club을 붙이려니 이렇게 나왔다.

No zones match blog.about20s.club

Workers에 커스텀 도메인을 붙이려면 그 도메인의 DNS를 Cloudflare가 관리하고 있어야 한다. 서브도메인 하나 붙이자고 실서비스 도메인의 네임서버를 통째로 옮기는 건 위험 대비 이득이 맞지 않았다. 옮기는 동안 문제가 생기면 블로그가 아니라 서비스가 멈춘다.

Pages는 다르다. 서브도메인이면 zone 없이 외부 DNS에 CNAME 하나만 추가하면 연결된다. 도메인 등록업체에 CNAME을 넣고 끝났다.

워크플로는 초록불인데 사이트에는 아무것도 없었다

wrangler pages deploy는 Pages 프로젝트가 미리 만들어져 있어야 동작한다. 없으면 실패한다. 대시보드에서 손으로 만들 수도 있지만, 앞서 막힌 게 대시보드였기 때문에 워크플로에서 처리하기로 했다.

문제는 프로젝트가 이미 있을 때도 생성 명령이 실패한다는 것이다. 두 번째 배포부터는 항상 에러가 난다. 그래서 이렇게 넣었다.

- name: Create Pages project
  continue-on-error: true # ← 여기
  run: npx wrangler pages project create blog --production-branch=main

continue-on-error: true는 이 스텝이 실패해도 워크플로를 계속 진행시킨다. “이미 있으면 나는 에러니까 무시하자”는 생각이었다.

워크플로가 성공으로 떴다. 그래도 사이트를 열어봤다. 아무것도 없었다.

커밋 기록을 보면 저 옵션을 넣고 6분 뒤다. API 토큰에 Pages 권한이 없었다.

Cloudflare 대시보드에서 토큰을 만들 때 “Edit Cloudflare Workers” 템플릿을 골랐는데, 이름과 달리 이 템플릿에는 Pages 권한이 들어 있지 않다. 그래서 프로젝트 생성이 권한 부족으로 실패했고, 프로젝트가 없으니 그다음 배포 스텝도 실패했다.

그런데 GitHub Actions에는 초록색 체크가 떴다. 첫 스텝이 continue-on-error로 넘어갔기 때문이다. 내가 무시하라고 지정한 건 “이미 존재함” 하나였는데, 실제로는 그 스텝의 모든 실패를 무시하게 만들었다.

이렇게 고쳤다.

- name: Ensure Pages project exists
  run: |
    set -o pipefail
    if npx wrangler pages project create blog --production-branch=main 2>&1 | tee /tmp/create.log; then
      echo "프로젝트를 생성했다."
    elif grep -qiE "already exists|8000007" /tmp/create.log; then
      echo "프로젝트가 이미 존재한다. 계속 진행한다."
    else
      echo "::error::프로젝트 생성 실패 — API 토큰에 Cloudflare Pages:Edit 권한이 있는지 확인할 것"
      exit 1
    fi

통과시킬 실패를 로그 내용으로 특정한다. “이미 존재함”이면 넘어가고, 나머지는 워크플로를 세운다. 그리고 세울 때 무엇을 확인해야 하는지 로그에 적어둔다. 몇 달 뒤에 이 에러를 다시 만나면 그때의 나는 이 맥락을 기억하지 못한다.

토큰은 커스텀으로 다시 만들어 Cloudflare Pages: Edit 권한을 직접 넣었다.

배운 것

continue-on-error는 스텝 단위로만 걸린다. “이 실패는 괜찮다”가 아니라 “이 스텝에서 뭐가 나든 괜찮다”는 뜻이다. 예상한 실패 하나를 넘기려고 걸면, 정작 알아야 하는 실패까지 같이 넘어간다.

CI가 초록색이라는 건 워크플로가 끝까지 돌았다는 뜻이지 결과물이 나갔다는 뜻이 아니다. 배포 워크플로라면 정말 배포됐는지 확인하는 단계가 마지막에 있어야 한다.

사람 손을 없애려고 고른 구조인데, 그 자동화가 조용히 실패한 걸 사람이 사이트를 열어봐서 찾았다. 6분 만에 잡은 것도 초록불을 안 믿었기 때문이다. 매번 그럴 거라고 기대할 수는 없으니, 배포된 페이지에 실제로 접속해보는 스텝을 워크플로 끝에 붙이는 게 다음 할 일이다.

Share:
Back to Blog