이 블로그는 한동안 루팅한 갤럭시 S9 한 대가 전부 떠받치고 있었다. Hugo로 만든 정적 파일을 폰 안의 Caddy가 내보내고, 글쓰기 화면과 조회수·댓글 API는 폰 안의 Node 서버가 처리하고, 바깥과는 Cloudflare Tunnel로 이어져 있었다. 잘 돌았지만 “폰이 꺼지면 블로그도 꺼진다"는 사실이 늘 마음에 걸렸다. 2026년 10월 10일, 이 구조를 끝내기로 했다. 이 글은 그 결정의 이유와 계획이고, 이어지는 시리즈는 실제로 옮기면서 겪은 일들이다.

왜 지금이었나

계기는 두 가지였다. 하나는 폰 쪽 감시 스크립트가 블로그 터널을 닷새 동안 17번이나 재시작한 기록을 본 것이다. 서비스는 대체로 살아 있었지만, 사람이 없을 때 폰 혼자 버티는 구조는 언제든 조용히 멈출 수 있었다. 다른 하나는 내가 AI 코딩 세션을 길게 쓰는 기간이 끝나간다는 점이었다. 그 전에 블로그를 “혼자서도 운영할 수 있는 모양"으로 만들어 두고 싶었다. 폰 안의 tmux 세션과 크론과 셸 스크립트를 유지보수하는 것보다, 관리 화면에서 글을 쓰고 GitHub에 저장되고 알아서 배포되는 쪽이 훨씬 단순하다.

원칙 하나: 폰을 거치지 않는다

처음 세운 원칙은 “폰을 최대한 안 거치는 방향"이었다. 글 읽기만 클라우드로 옮기고 글쓰기와 통계는 폰에 남겨 두는 절충안도 있었지만, 그러면 폰 의존이 사라지지 않는다. 그래서 목표를 이렇게 적었다.

  • 글 보여 주기, 조회수·댓글·방문 기록, 글쓰기 화면, 글 올리기 API, 예약 글 자동 공개까지 전부 Cloudflare와 GitHub만으로 돌아갈 것
  • 폰이 꺼져도 블로그는 아무 영향이 없을 것
  • 폰에 남길 것은 폰 자체를 점검하는 “서버 상태” 화면 하나뿐. 이것은 블로그 기능이 아니라 폰 관리용이다

도구 선택: Pages가 아니라 Workers

정적 사이트라면 Cloudflare Pages가 먼저 떠오르지만 Workers를 골랐다. 이유는 하나의 코드로 정적 파일 서빙, API, 관리 화면, 10분마다 도는 cron까지 다 처리할 수 있기 때문이다. Workers의 정적 자산(assets) 기능에 run_worker_first를 켜면 모든 요청이 내 코드를 먼저 거치므로, 경로에 따라 정적 파일을 내주거나 데이터베이스를 읽거나 GitHub에 커밋하는 분기를 한 곳에서 쓸 수 있다. 데이터는 D1(서버리스 SQLite), 글 원본은 지금처럼 GitHub 저장소 하나다.

4단계 계획

한 번에 바꾸면 되돌리기가 어려우니 단계를 나눴다. 각 단계는 PR 하나로 올리고, 문제가 생기면 그 PR만 되돌리면 되게 했다.

  1. 조회수·댓글·방문 기록을 Worker + D1로. 브라우저가 부르는 API 주소는 그대로 두고 뒤에서 처리하는 쪽만 바꾼다.
  2. 글 올리기 API를 Worker로. 글과 이미지를 GitHub에 커밋하면 Cloudflare가 자동으로 빌드한다.
  3. 관리 화면을 Worker로. Cloudflare Access 로그인 뒤에서만 열리게 한다.
  4. 옛 주소(blog.mhub.uk)를 Worker가 인수. 서버 상태 화면만 폰으로 보내고 나머지는 전부 새 주소로 이동시킨다. 폰이 받던 모든 예비 경로를 없앤다.

실제로는 1~3단계가 10월 11일 하루에 끝났고, 4단계는 자동 글 루틴의 토큰을 맞추는 작업과 엮여 10월 12일 밤에 마무리됐다. 그 사이의 이틀은 두 서비스가 같은 글을 따로 내보내는 묘한 기간이었는데, 그 이야기는 따로 쓴다.

미리 정해 둔 규칙

옮기는 동안 지키기로 한 것도 있다. 비밀값(토큰, 시크릿)은 채팅에도 커밋에도 문서에도 쓰지 않고 이름만 적는다. 방문자 IP는 저장은 하되 어디에도 공개하지 않는다. 사용자 승인이 필요한 일은 무엇이 바뀌고 되돌릴 수 있는지 먼저 설명한 뒤에 한다. 검증은 “열어 보고 알려 달라"가 아니라 curl과 테스트와 헤드리스 브라우저로 직접 한다. 이 규칙들은 결과적으로 이전 작업 전체의 품질을 결정했다.

다음 글은 가장 먼저 부딪힌 문제, Cloudflare의 빌드 환경에서 폰과 똑같은 Hugo 결과물을 만드는 이야기다.