[blog] Jekyll 블로그 자동 발행 파이프라인 구축기

왜 자동화가 필요했을까

시리즈 글 안내

지금 운영 중인 블로그는 GitHub 저장소에 Markdown 파일을 올리고, Jekyll이 정적 사이트로 빌드하는 구조입니다.

이 방식은 단순하고 안정적이지만, 글을 하나 올릴 때마다 아래 과정을 반복해야 했습니다.

  1. 읽을 만한 개발 글을 찾는다.
  2. 핵심 내용을 정리한다.
  3. Markdown 포스트 형식으로 변환한다.
  4. front matter를 맞춘다.
  5. 빌드가 깨지지 않는지 확인한다.
  6. git push 한다.

문제는 이 흐름이 한두 번은 괜찮아도, 꾸준히 운영하려면 생각보다 손이 많이 간다는 점입니다.

특히 “좋은 글을 찾는 시간”과 “형식을 맞추는 시간”이 누적되기 시작하면, 정작 중요한 읽기와 정리는 뒤로 밀리기 쉽습니다.

그래서 이번에는 단순히 글을 쓰는 블로그가 아니라, 좋은 개발 콘텐츠를 선별해서 가공하고 자동으로 발행하는 파이프라인으로 바꿔보기로 했습니다.

한마디로 정리하면, 이번 작업의 목표는 글쓰기 자동화가 아니라 블로그 운영 자동화였습니다.

목표

이번 자동화의 목표는 아래 4가지였습니다.

  • 고품질 개발 소스를 자동으로 수집할 것
  • 원문을 복제하지 않고 요약/맥락 중심으로 Markdown을 만들 것
  • 잘못된 글 형식이나 빌드 오류를 자동으로 잡을 것
  • 최종적으로 GitHub Actions가 commit/push까지 처리할 것

즉, 단순 크롤링이 아니라 수집 -> 가공 -> 검증 -> 발행이 한 번에 이어지는 구조를 만들고 싶었습니다.

전체 구조

현재 파이프라인은 아래 흐름으로 동작합니다.

RSS/공식 블로그 수집
  -> 점수화/중복 제거
  -> Markdown 생성
  -> 형식 검증
  -> Jekyll build
  -> GitHub Actions commit/push

이 구조를 만들면서 가장 신경 쓴 부분은 “중간 단계 하나라도 깨지면 다음 단계로 넘어가지 않게 하는 것”이었습니다.

1. 콘텐츠 수집

scripts/generate_dev_digest.py가 RSS 기반으로 개발 관련 글을 가져옵니다.

현재는 다음과 같은 소스를 기준으로 수집합니다.

  • GitHub Blog
  • Spring Blog
  • JetBrains Blog
  • Cloudflare Blog
  • InfoQ

아무 글이나 가져오는 것이 아니라, 최신성/중복 여부/기술 키워드 기준으로 점수를 계산해서 상위 항목만 선택합니다.

예를 들어 공식 블로그이거나, 최근 며칠 안에 올라왔거나, 현재 블로그 주제와 맞는 키워드를 포함한 글에 더 높은 점수를 주는 방식입니다.

스크린샷 포인트 1: GitHub Actions 실행 화면에서 digest 생성 로그

2. Markdown 생성

선택된 결과는 Jekyll 포스트 형식으로 _posts/dev/digest/ 아래에 생성됩니다.

예를 들어 아래와 같은 front matter를 포함합니다.

---
layout: posts
title: "[dev] 주간 기술 아티클 다이제스트"
date: 2026-03-16 11:46:48 +0900
categories:
  - dev
  - digest
tags:
  - ai
  - java
---

실제로는 Python 스크립트가 날짜, 카테고리, 태그, 본문 섹션을 함께 만들어 줍니다.

posts_dir = repo_root / "_posts" / "dev" / "digest"
file_name = f"{today_kst.strftime('%Y-%m-%d')}-dev-digest.markdown"
target_post = posts_dir / file_name

markdown = build_markdown(today_kst, selected, selected_tags)
target_post.write_text(markdown, encoding="utf-8")

여기서 중요한 점은 원문 전체를 그대로 재배포하지 않는다는 점입니다.

포스트에는 아래 정보만 넣습니다.

  • 출처
  • 발행일
  • 원문 링크
  • 한 줄 요약
  • 왜 중요한가

이렇게 하면 블로그 콘텐츠 품질은 유지하면서도, 원문 전체 복제보다 더 제한된 형태로 정보를 다루게 됩니다. 다만 RSS/Atom summary를 활용하는 경우가 있어, 이 부분은 항상 보수적으로 운영하는 것이 좋습니다.

또한 블로그에 올라오는 글의 형식이 일정해져서, 읽는 입장에서도 “어떤 정보를 기대하면 되는지”가 명확해집니다.

스크린샷 포인트 2: 실제 생성된 _posts/dev/digest/*.markdown 파일 화면

3. 검증

자동 생성은 편하지만, 형식이 조금만 틀어져도 Jekyll 빌드가 깨질 수 있습니다.

그래서 scripts/validate_dev_digest.py를 추가해서 아래를 검사하게 했습니다.

  • front matter 존재 여부
  • layout, categories, tags 같은 핵심 필드 존재 여부
  • 필수 섹션 존재 여부
  • 최소 항목 개수
  • 링크 형식

자동 생성이 되었더라도 검증을 통과하지 못하면 publish 단계로 넘어가지 않도록 했습니다.

이 부분은 생각보다 중요합니다. 자동화는 편하지만, 한 번 잘못된 형식이 들어가면 이후 단계가 모두 무의미해질 수 있기 때문입니다.

python3 scripts/validate_dev_digest.py _posts/dev/digest/YYYY-MM-DD-dev-digest.markdown --min-items 1

4. GitHub Actions 발행

.github/workflows/dev-content-pipeline.yml에서 실제 자동 발행을 처리합니다.

동작 순서는 다음과 같습니다.

  1. 저장소 checkout
  2. Python 환경 준비
  3. digest 생성
  4. 생성된 Markdown 검증
  5. Jekyll build 수행
  6. 변경 사항이 있을 때만 commit/push

여기서 핵심은 Markdown 생성 성공이 아니라 Jekyll build 성공을 기준으로 발행한다는 점입니다.

글 파일만 생겼다고 끝나는 것이 아니라, 블로그 전체가 실제로 렌더링 가능한 상태여야 최종 발행됩니다.

워크플로 관점에서는 아래 단계가 핵심입니다.

- name: Generate digest post
  run: python3 scripts/generate_dev_digest.py --max-items 6

- name: Resolve latest generated post
  run: latest_post="$(find _posts/dev/digest -name '*-dev-digest.markdown' | sort | tail -n 1)"

- name: Validate generated digest
  run: python3 scripts/validate_dev_digest.py "$latest_post" --min-items 1

- name: Build site
  env:
    JEKYLL_ENV: production
  run: bundle exec jekyll build

그리고 자동 commit/push를 넣을 때는 한 가지 더 고려할 점이 있습니다. 워크플로가 같은 브랜치에 다시 push 하기 때문에, 저장소 권한(contents: write)과 브랜치 보호 정책, 그리고 후속 워크플로 트리거 방식까지 함께 봐야 운영이 안정적입니다.

스크린샷 포인트 3: dev-content-pipeline.yml 실행 성공 화면

품질 게이트도 따로 추가했다

자동 발행 외에도, 블로그 자체 품질을 지키기 위한 워크플로를 별도로 두었습니다.

.github/workflows/site-quality-gates.yml에서는 아래를 검사합니다.

  • bundle exec jekyll build
  • proof-html 기반 링크/HTML 검사
  • bundler-audit 기반 Ruby 의존성 보안 검사

실패 시에는 _site와 audit 결과를 artifact로 남길 수 있도록 구성해서, 가능하면 나중에 원인을 추적하기 쉽게 만들었습니다. 다만 빌드가 아주 이른 단계에서 실패하면 _site 아티팩트는 생성되지 않을 수 있습니다.

즉, 발행 파이프라인은 “새 글을 올리는 일”에 집중하고, 품질 게이트는 “사이트 전체 품질을 지키는 일”에 집중하도록 역할을 분리했습니다.

구현하면서 만난 문제

이번 작업에서 가장 까다로웠던 부분은 블로그 글 생성이 아니라 로컬 Ruby 빌드 환경 복구였습니다.

특히 다음 문제가 있었습니다.

  • rbenv Ruby 바이너리가 다른 사용자 경로를 바라보는 문제
  • eventmachine, ffi 같은 native gem 빌드 문제
  • macOS Command Line Tools와 C++ 헤더 경로 문제

결국 scripts/ruby_cxx_fix.rbscripts/jekyll-build.sh를 추가해 로컬에서도 bundle installjekyll build가 다시 가능하도록 정리했습니다.

이 문제는 단순히 “명령어가 실패했다” 수준이 아니라, 블로그 자동화가 장기적으로 유지될 수 있느냐를 결정하는 문제였습니다.

이 과정에서 느낀 점은, 자동화는 스크립트 몇 줄 추가보다 재현 가능한 개발 환경을 만드는 일이 훨씬 중요하다는 것입니다.

스크린샷 포인트 4: 로컬 npm run jekyll:build 성공 로그

이번 구조의 장점

현재 구조의 장점은 아래와 같습니다.

  • 사람이 매번 수작업으로 정리하지 않아도 된다.
  • 글 생성 규칙이 일정해서 품질 편차가 줄어든다.
  • 빌드와 링크 검사를 통과한 결과만 발행된다.
  • 블로그가 단순 글 저장소가 아니라 운영 가능한 시스템이 된다.

  • 사람이 컨텐츠 운영보다 더 중요한 “운영 구조 개선”에 시간을 쓸 수 있다.

결국 블로그 운영의 핵심이 “글을 올리는 행위”에서 “좋은 콘텐츠를 지속적으로 발행하는 시스템”으로 바뀌게 됩니다.

앞으로 개선하고 싶은 점

아직 개선할 부분도 남아 있습니다.

  • digest 외에 일반 기술 글 초안도 템플릿 기반으로 생성하기
  • 소스별 신뢰도 가중치 고도화
  • 중복 주제 감지 강화
  • 대표 이미지/요약 메타데이터 자동 생성
  • 인기 태그 중심의 큐레이션 전략 추가

장기적으로는 이 블로그를 단순한 GitHub Pages 사이트가 아니라, 개발 지식을 축적하고 자동 발행하는 개인 미디어 시스템으로 발전시키는 것이 목표입니다.

마무리

이번 작업을 통해 얻은 가장 큰 변화는 “글을 쓰는 블로그”에서 “발행 프로세스를 설계한 블로그”로 한 단계 넘어갔다는 점입니다.

자동화는 시간을 아끼기 위한 도구이기도 하지만, 더 중요한 것은 꾸준히 운영할 수 있는 구조를 만든다는 데 있습니다.

다음 글에서는 이번에 실제로 겪었던 Ruby/rbenv + Jekyll 빌드 문제를 어떻게 해결했는지를 좀 더 자세히 정리해보려고 합니다.

운영을 해보니, 개인 블로그를 오래 유지하는 힘은 글쓰기 의지보다도 “반복되는 과정을 얼마나 시스템으로 바꿨는가”에서 나온다는 생각이 들었습니다.

함께 읽으면 좋은 글

jekyll github-actions github-pages automation python

2026

맨 위로 이동 ↑

2023

[linux] shell script version compare

최대 1 분 소요

개요 linux를 사용하다 보면 version 비교하는 기능이 필요합니다. 특히 기존 설치된 패키지의 version을 확인하여 업데이트할 경우가 있겠죠. 아래와 같이 간단한 shell script로 구현할 수 있습니다.

[jenkins] jenkins docker install

1 분 소요

ci/cd 오픈소스 도구로 가장 많이 사랑 받는 jenkins에 대해 포스팅 해보겠습니다. 먼저 설치부터 해야겠지요? 항상 패키지 매니저로 설치했었는데 이번에는 docker로 설치해보도록 하겠습니다.

[springboot] springboot history

2 분 소요

springboot 탄생 배경 springboot란 spring framework를 좀 더 쉽게 개발/배포할려는 목적으로 만들어 졌습니다. 2012년 Mike Youngstrom은 spring 프레임워크에서 컨테이너 없는 웹 애플리케이션 아키텍처에 대한 지원을 요청하는 spring...

[springboot] springboot3 querydsl 적용

1 분 소요

개요 springboot3로 메이저 업그레이드 되면서 JPA + querydsl 셋팅 환경에 변화가 생겼습니다. 기존 의존성으로는 작동하지 않고 jakarta classification을 추가해야 작동하는 이슈가 발생합니다. springboot3부터 javax -> jakar...

[springboot] springboot3 migration

최대 1 분 소요

개요 2022년 하반기에 springboot3가 공식 release 되었습니다. springboot2가 2018년 상반기에 release되고 나서 새롭게 판올림 버전으로 가장 큰 변화로는 아래와 같습니다. spring framework 6 적용 최소 사양 JDK 17 ...

[springboot] springboot initializer

최대 1 분 소요

개요 항상 intellij ultimate 버전만 사용하고 있었는데 무슨 바람이 난건지.. intellij ce 버전에 도전하였습니다. springboot 프로젝트 생성이며.. 그 밖에 기본적으로 될꺼라 싶은것 중에 안되는 녀석들도 꽤 있더군요. 이번 시간엔 간단하게 spingbo...

[querydsl] querydsl No release for a long time

최대 1 분 소요

개요 JPA를 spring data jpa + querydsl과의 조합으로 접하는 경우가 많습니다. spring data jpa에서 제공해주는 specification으로도 충분히 해낼수 있지만 querydsl에 비할바는 아닙니다. entity에 wrapper Q클래스를 생성하여 ...

[jekyll] jekyll install

6 분 소요

개요 오랫동안 방치했던 블로그를 다시 열면서 jekyll를 다시 설치해봤습니다. 설치 jekyll 프로젝트로 이동하여 아래 명령어를 입력합니다. gem install jekyll bundler Fetching pathutil-0.16.2.gem Fetching terminal-t...

맨 위로 이동 ↑

2021

[linux] Parse yaml

최대 1 분 소요

bash를 사용하여 yaml 파일을 파싱 및 환경 변수로 손쉽게 등록할 수 있습니다.

[유틸리티] Mock Http Status Test

최대 1 분 소요

외부 통신에 대한 Error 처리는 앱을 더욱 더 견고하게 만들 수 있습니다. Error 처리를 위해 엔드포인트에 대한 Http Status Code를 억지로 생성하는것은 매우 귀찮은 일이라고 할까요? 보다 간편하게 Mock 서버를 두는게 더 효율적이라고 볼 수 있습니다.

맨 위로 이동 ↑