포스트

기술 블로그 인프라 구성: Jekyll + GitHub Actions + S3 + CloudFront

기술 블로그 인프라 구성: Jekyll + GitHub Actions + S3 + CloudFront

요약

  • Jekyll(Chirpy)로 정적 사이트를 만들고 GitHub Pages로 배포한다
  • main 브랜치에 push하면 GitHub Actions가 빌드, 검증, 배포를 자동으로 수행한다
  • 이미지는 S3 + CloudFront로 statics.jun0.dev에 분리해서 서빙한다

이렇게 구성한 이유

포트폴리오용 기술 블로그를 운영하다 보니, 글을 올릴 때마다 배포나 이미지 관리가 신경 쓰였다. 그래서 정적 사이트는 GitHub Pages로 배포하고, 이미지는 S3 + CloudFront로 분리해서 statics.jun0.dev로 서빙하는 구조로 잡았다. 글 작성은 _posts/에만 집중하고, 배포는 GitHub Actions가 처리하는 형태가 목표였다.

전체 구조

1
2
3
4
5
6
7
8
글 작성(_posts/*.md)
  ↓
main 브랜치에 push
  ↓
GitHub Actions: Jekyll 빌드 → htmlproofer 검증 → GitHub Pages 배포
  ↓
logs.jun0.dev (사이트)
statics.jun0.dev (이미지 CDN: S3 + CloudFront)

정적 사이트

  • Jekyll + Chirpy 테마로 정적 사이트를 생성한다
  • 사이트는 GitHub Pages로 배포해 logs.jun0.dev에서 제공한다

로컬에서는 Jekyll 서버로 확인하고, 실제 배포는 CI에서 빌드한 결과물을 기준으로 한다.

CI/CD: GitHub Actions

main 브랜치에 push하면 GitHub Actions가 빌드, 검증, 배포를 자동으로 처리한다. 필요하면 수동 실행도 가능하다.

여기서 정리한 포인트는 아래다.

  • 빌드 결과물을 바로 배포하지 않고, 내부 링크 검증을 통과한 경우에만 배포한다
  • 빌드 job과 배포 job을 분리해서, 빌드 실패 시 배포가 진행되지 않는다
  • 동시 배포는 하나만 허용하고, 새 배포가 들어오면 기존 배포는 취소되도록 설정했다

아래는 현재 사용 중인 GitHub Actions 워크플로우다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
name: "Build and Deploy"
on:
  push:
    branches:
      - main
      - master
    paths-ignore:
      - .gitignore
      - README.md
      - LICENSE

  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Pages
        id: pages
        uses: actions/configure-pages@v4

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.3
          bundler-cache: true

      - name: Build site
        run: bundle exec jekyll b -d "_site$"
        env:
          JEKYLL_ENV: "production"

      - name: Test site
        run: |
          bundle exec htmlproofer _site \
            --disable-external \
            --ignore-urls "/^http:\\/\\/127.0.0.1/,/^http:\\/\\/0.0.0.0/,/^http:\\/\\/localhost/"

      - name: Upload site artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: "_site$"

  deploy:
    environment:
      name: github-pages
      url: $
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

이미지 CDN: S3 + CloudFront

블로그에 들어가는 이미지는 statics.jun0.dev로 분리해서 서빙하고 있다.

  • 저장소: AWS S3
  • CDN: CloudFront
  • 도메인: statics.jun0.dev

이렇게 분리한 이유는 아래와 같다.

  • 정적 사이트 저장소에 이미지까지 넣지 않아도 돼서, 저장소와 배포 흐름을 단순하게 유지할 수 있다
  • CloudFront를 앞에 두면 캐시를 활용할 수 있고, 원본 S3로 가는 트래픽도 줄일 수 있다
  • 트래픽 기반 과금 구조로 비용 부담이 낮다 (거의 발생 X)

글 본문에서는 https://statics.jun0.dev/... 형태로 이미지를 참조한다.

정리

  • 정적 사이트는 Jekyll + Chirpy로 구성하고, GitHub Pages로 배포한다
  • main push 시 GitHub Actions로 빌드, 내부 링크 검증, 배포까지 자동화했다
  • 이미지는 S3 + CloudFront로 CDN을 구성해서 사이트 저장소와 분리했다
  • 글은 _posts/에 쌓이고, 하단 형식을 통일해 프로젝트 맥락을 정리한다

프로젝트: 포트폴리오 블로그(logs.jun0.dev) 관련 링크

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.