이번 설 연휴, 블로그 개발을 한창 하고 있었는데 GitHub CI/CD가 Fail이 떴습니다. 별일 아닐 거라고 생각하고 github action workflow를 확인해보니 뭔가 이상했습니다.

GitHub Actions CI/CD Failyour spending limit needs to be increased

싸늘합니다. GitHub Actions는 무료 한도가 월 2,000분이라고요. 내가 이걸 넘겼다고? 현실 부정을 하게 됐습니다. Billing에 들어가봅니다.

GitHub Actions Billing거짓말이라고 말해

네, 무료 한도 2,000분을 전부 소진했습니다. 리셋까지 14일이 남았네요.

저는 2가지를 분석해야 했습니다.

  • 왜 이렇게 action time을 많이 소비했는가
  • 앞으로는 어떻게 해야 소비를 줄일 수 있는가

이렇게 써놓으니 반성문 같네요. 아무튼 위 내용을 핵심 과제로 이번 포스트를 정리해보겠습니다.

GitHub Actions는 어떻게 과금을 따지는지 살펴보겠습니다.

적을 알고 나를 알면 백전백승

일단 가장 먼저 GitHub Actions의 무료 할당량부터 확인해보았습니다. Pro로 올린다면 좀 더 쾌적해질까? 혹시 무료가 2,000분이 아닌 건 아닐까? 현실 부정 + 회피형처럼 접근해보겠습니다.

https://docs.github.com/en/billing/managing-billing-for-github-actions/about-billing-for-github-actions

GitHub Free 플랜은 private 레포지토리에서 월 2,000분의 Actions 시간을 제공합니다.

플랜월별 무료 분스토리지
Free2,000분500 MB
Pro3,000분1 GB
Team3,000분2 GB
Enterprise Cloud50,000분50 GB

Public 레포지토리는 완전 무료입니다. 표준 GitHub-hosted runner를 사용하면 분 한도에 포함되지 않아요. Private 레포지토리에서만 분 소비가 발생합니다.

GitHub rounds the minutes and partial minutes each job uses up to the nearest whole minute.

출처: GitHub - About billing for GitHub Actions

GitHub Actions 과금의 첫 번째 함정은 분 단위 올림입니다. Job이 10초만 실행되어도 1분으로 계산됩니다. 초 단위 과금이 아니라 분 단위 올림이라는 점이 핵심입니다.

실제로 제 프로젝트에서 겪은 사례가 있습니다. PR을 올릴 때마다 자동으로 label을 다는 labeler 워크플로우를 사용하고 있습니다. (참고: https://blog.huns.site/blog/posts/dev/research/github-labeler-automation) 이 job은 5~10초면 끝납니다. 그런데 매번 push할 때마다 이 job이 트리거되고, 그때마다 1분씩 과금됩니다. 하루에 10번 push하면 labeler만으로 10분이 소비되는 셈이에요. 실제로는 합쳐서 2분도 안 걸리는 작업인데요.

두 번째 함정은 병렬로 실행되는 job이 각각 독립적으로 분 단위 올림된다는 점입니다.

예를 들어 이런 워크플로우가 있다고 해볼게요.

workflow.yml
jobs:
  lint:        # 15초 걸림
    runs-on: ubuntu-latest
    steps: ...
  type-check:  # 20초 걸림
    runs-on: ubuntu-latest
    steps: ...
  build:       # 25초 걸림
    runs-on: ubuntu-latest
    steps: ...

total duration(전체 실행 시간)은 25초(가장 긴 job 기준)지만, GitHub 과금은 이렇게 계산됩니다.

Job실제 시간과금 시간
lint15초1분
type-check20초1분
build25초1분
합계25초3분

25초짜리 작업이 3분으로 과금됩니다. 병렬 job이 많을수록 이 격차는 극적으로 벌어져요.

찾아보니 GitHub 커뮤니티에서도 이 정책에 대한 불만이 많았습니다. 16개 job이 각각 4~6초 실행되어 실제 1분이지만 16분으로 청구된 눈물나는 사례도 있었습니다.

https://github.com/orgs/community/discussions/8726

"빠르게 끝내려고 병렬로 쪼개면 오히려 과금이 늘어날 수 있다." 각 job이 독립 VM에서 실행되기 때문에 VM 설정/해제 시간까지 포함되면 실제 소요 시간도 생각보다 깁니다.

이번에 제가 2,000분을 소진한 가장 큰 원인이 바로 이거였습니다. AI 자동화 loop로 PR을 잘게 쪼개면서 매 PR마다 lint, type-check, build를 병렬 job으로 돌렸는데, 짧은 job들이 각각 1분씩 과금되니 순식간에 분이 사라졌어요.

GitHub Actions에서 기본 Linux 이외의 OS를 사용하시는 분들은 많지 않으실 거라 생각합니다.

세 번째 함정은 runner의 운영체제에 따라 분 배율이 다르다는 것입니다.

Runner OS분 배율10분 실행 시 과금
Linuxx110분
Windowsx220분
macOSx10100분

Linux에서 10분 걸리는 작업을 macOS에서 돌리면 100분으로 계산됩니다. 같은 작업인데 OS만 다르면 10배 차이가 나요. (GitHub - Minute Multipliers)

2026년 1월부터 GitHub은 runner 가격 체계를 개편해서 분당 요금제로 전환했습니다. (GitHub Actions Runner Pricing) 실제 단가를 보면 배율이 더 명확하게 드러납니다.

Runner분당 요금 (USD)
Linux 2-core (x64)$0.006
Windows 2-core (x64)$0.010
macOS 3-core (M1)$0.062

제 경우에는 모든 프로젝트가 웹 기반이라 Linux runner만 사용하고 있어서 OS 배율 문제에 해당되지 않았습니다. 하지만 배율이 2배, 10배나 차이난다는 게 신기해서 어떤 경우에 macOS/Windows를 써야 하는지 조사해봤습니다.

대부분의 웹 개발 CI/CD는 Linux runner로 충분합니다. Node.js, Python, Ruby, Docker 등은 전부 Linux에서 돌아가거든요.

macOS runner가 필수인 경우는 제한적입니다.

Windows runner가 필요한 경우도 마찬가지로 특수합니다.

  • .NET Framework 앱 → Windows 전용 API 사용 시
  • Windows 환경 테스트 → Windows에서만 발생하는 버그 재현

웹 개발자라면 runs-on: ubuntu-latest가 99%의 상황에서 정답입니다. macOS runner를 아무 생각 없이 쓰고 있었다면, Linux로 바꾸는 것만으로 과금이 10분의 1로 줄어들어요.

최근 AI를 활용한 개발 자동화를 시도하고 있습니다. Claude Code로 코드를 생성하고 작업 단위마다 자동으로 PR을 올리는 ralph loop를 구성했죠. 이 과정에서 아래 두 가지가 겹쳐서 Actions 분이 폭발적으로 늘었어요.

  1. PR을 잘게 쪼개는 전략 → AI가 작은 단위로 PR을 open/merge하게 지시했습니다. PR 수 자체가 크게 늘었습니다.
  2. PR마다 빌드 체크 → 매 PR에서 lint, type-check, build를 병렬 job으로 돌렸습니다.

PR 하나에 병렬 job 3개가 돌아가면 최소 3분입니다. 하루에 10개 PR이면 30분, 한 달이면 900분이에요. 설 연휴에 시작한 토이 프로젝트는 하루에 PR을 100개씩 만들었으니 오죽할까요. 이렇게 되니 2,000분을 빠르게 채운 게 이해가 됩니다.

반면 이 블로그 레포지토리의 워크플로우는 상대적으로 가벼운 편입니다.

Workflow트리거실제 시간과금
PR LabelerPR open/sync~5~10초1분
Auto AssignPR open~5~10초1분
Deploy (S3 + CloudFront)push to main~7분7분

main merge가 아닌 PR에서는 labeler와 auto assign만 돌아갑니다. 빌드 체크는 main merge 시에만 실행하고 있어요. 그래서 PR 하나당 과금은 2분 정도로, 다른 프로젝트 대비 소비가 적었습니다. Ralph Loop를 시도한 프로젝트도 아니었고요.

모든 PR에서 빌드 체크를 돌리는 게 이상적입니다. build-time 에러를 main stream에 녹이기 전에 파악하는 것이 좋죠. 하지만 무료 플랜에서는 actions time을 소진하게 됩니다. 이 딜레마의 해결책은 뒤에서 다루겠습니다.

앞서 분석한 대로 병렬 job은 각각 1분 최소 과금이 붙습니다. 10초짜리 job 3개를 병렬로 돌리면 3분이지만, 하나의 job에 step으로 합치면 30초 → 1분입니다.

workflow.yml (Before)
# 3개 병렬 job → 최소 3분 과금
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm lint
  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm tsc --noEmit
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install
      - run: pnpm build

직렬로 합치면 단순합으로 30초면 됩니다. 과금은 1분이죠.

pnpm install도 한 번만 실행하면 됩니다. 각 job마다 checkout → install을 반복하는 낭비가 사라져요.

workflow.yml (After)
# 1개 job에 step으로 합침 → 최소 1분 과금
jobs:
  ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install    # 한 번만 설치
      - run: pnpm lint
      - run: pnpm tsc --noEmit
      - run: pnpm build

"빠른 피드백을 위해 병렬로!" 라는 의식으로 job을 쪼개며 고도화를 했었습니다. 지금은 생각이 달라졌습니다. 시간 효율에 비해, 비용 효율이 안 좋습니다. 잃는 것에 비해 얻는 게 영 적어요. 각 job이 그리 크지 않기 때문에 직렬도 그리 오래 걸리지 않습니다. 이전 방식으로 돌아가기로 했습니다.

GitHub Actions에서 배포하면 매번 7분씩 소비됩니다. 이 블로그 프로젝트에서는 pnpm ship 명령어로 로컬에서 직접 S3 + CloudFront 배포를 실행할 수 있도록 만들어뒀어요.

Terminal
# 로컬에서 빌드 + 배포 + CloudFront 무효화 + 검증까지 한번에
pnpm ship
 
# 이미 빌드가 되어 있으면 빌드 스킵
pnpm ship:only

당연하지만 로컬 배포는 GitHub Actions 분을 소비하지 않습니다. 그런데 비용 절약을 위한 로컬로의 전환 덕분에 의외의 이점들을 더 체감하고 있습니다.

압도적인 속도 차이

GitHub Actions에서 7분 걸리는 배포가 로컬에서는 1분도 안 걸립니다. 이유는 간단한데요, GitHub이 제공하는 hosted runner는 동시에 1개만 실행되지만, 로컬 머신에서는 빌드부터 S3 동기화까지 병렬 작업이 자유롭게 이루어지기 때문입니다. 제 로컬에서는 10개의 runner에 의해 작업이 동시에 돌아가니 빌드/업로드 속도 자체가 차원이 달라요.

GitHub Actions의 CD 파이프라인은 main 브랜치에 고정했습니다. 하지만 로컬 배포는 현재 체크아웃된 브랜치가 뭐든 상관없이 실행할 수 있어요. feature 브랜치에서 작업 중일 때도 pnpm ship으로 바로 배포해서 배포 환경에서 확인할 수 있습니다.

현재는 운영 환경 1개만 있지만, 검증 환경을 따로 둘 수도 있겠다고 가능성을 봤습니다. 업로드 폴더를 script로 구분하는 거예요. 별도 도메인으로 검증 배포 환경을 구성해두면, main에 merge하기 전에 배포 결과를 미리 검증해볼 수 있겠죠. 아직은 없지만 매우 유용할 것 같네요.

불필요한 워크플로우 실행을 줄이는 것도 중요합니다.

README나 문서만 수정했는데 빌드가 돌아가는 건 낭비입니다. paths 필터로 실제 코드/콘텐츠가 바뀔 때만 실행되게 제한할 수 있어요.

workflow.yml
on:
  pull_request:
    paths:
      - 'src/**'
      - 'posts/**'
      - 'package.json'
      - 'pnpm-lock.yaml'

같은 브랜치에서 연속 push가 발생하면 이전 실행을 취소합니다.

workflow.yml
concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: true

이미 적용해둔 설정인데, 이게 없었으면 분 소비가 훨씬 심했을 거예요.

${{ github.ref }}는 github에서 사용하는 고정 변수이므로 그대로 넣어주면 됩니다.

의존성 설치(pnpm install)는 매번 네트워크에서 패키지를 받아오면 시간이 오래 걸립니다. actions/setup-node의 cache 옵션이나 actions/cache를 사용하면 이전 빌드의 의존성을 재사용할 수 있어요.

workflow.yml
- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'pnpm'   # pnpm store를 자동 캐싱

이미 적용해뒀지만, 캐시 히트 여부에 따라 install 시간이 30초에서 5초로 줄어들기도 합니다.

Matrix 빌드를 사용하거나 다단계 job 체인을 구성할 때, 앞선 job이 실패하면 뒤의 job을 실행할 필요가 없습니다.

workflow.yml
strategy:
  fail-fast: true   # 하나가 실패하면 나머지 취소
 
# 또는 needs로 의존성 체인
jobs:
  build:
    runs-on: ubuntu-latest
    steps: ...
  deploy:
    needs: build   # build가 실패하면 deploy는 실행 안 됨
    runs-on: ubuntu-latest
    steps: ...

가장 간단한 방법은 PR을 잘게 쪼개지 않고 뭉쳐서 올리는 겁니다. PR의 횟수에 따라 GitHub Actions 분을 소비하기 때문이죠.

하지만 비용 때문에 PR을 뭉쳐서 올리는 건 PR 품질과 개발 속도를 희생하는 방향이라 좋은 해결책이 아닙니다. 이건 개발자의 자존심입니다. 차라리 돈을 내고 말죠.

그렇다면 저는 어떻게 이 문제를 해결했을까요?

제가 내린 결론은 로컬과 GitHub Actions를 역할 분리하는 것입니다.

단계실행 환경이유
린트/타입 체크로컬 (pre-commit hook)빠르고 비용 무료
빌드 테스트로컬 (pre-push hook)push 전에 검증
배포 (개발 중)로컬 (pnpm ship)Actions 분 절약
배포 (최종)GitHub Actions자동화, 기록
PR 라벨링/할당로컬 (label-pr.sh)Actions 워크플로우 대체

현재는 pre-commit hook에서 lint와 type-check만 돌리고 있습니다. 여기에 pre-push hook에서 빌드 테스트를 추가하면, GitHub Actions에서 빌드 체크를 돌리지 않아도 push 전에 문제를 잡을 수 있어요. CI에서 중복 실행할 필요가 없어지는 거죠.

.husky/pre-push
#!/usr/bin/env sh
pnpm build

이렇게 하면 모든 PR에서 빌드 체크를 돌리고 싶다는 목표와 Actions 비용을 줄이고 싶다는 목표를 동시에 달성할 수 있습니다.

간단한 설정입니다. 하지만 확실합니다. 이번 GitHub Actions 무료 소진 경험이 아니었다면 할 필요도 없던 설정이었는데요, 경각심을 얻었습니다.

무료 플랜의 핵심은 "GitHub Actions에서 꼭 해야 하는 것만 Actions에서 하기"라고 느낍니다.
로컬에서 할 수 있는 건 로컬에서 해봐요. 경제적이고 빠릅니다.

그럼에도 불구하고 GitHub Actions에서 모든 체크를 돌려야 하는 경우가 있을 것 같습니다. 팀원과 함께 일을 하는 경우, 더 넓게는 외부 Issue 및 Contribution을 받는 경우가 있겠죠. 프로젝트 차원에서 lefthook, husky 등의 git hook을 설정한다면, 반대로 이걸 주석 처리하고 임시 push할 수도 있겠죠. (실제로 있는 일입니다.)

github workflow file까지 제거하는 경우는 무시합니다. 이 경우엔 좀 더 복잡한 설정을 해야 하겠죠.

일단 저는 개인 프로젝트 위주이다보니 이런 경우는 논외로 하겠습니다.

가장 확실한 절약법은 레포지토리를 public으로 전환하는 것입니다. Public 레포지토리에서 실행되는 GitHub Actions는 무료 분에 포함되지 않아요.

다만 secrets(API 키, 인증 정보)가 노출되지 않도록 주의해야 합니다. (Security Hardening Guide) GitHub Actions의 secrets는 fork된 PR에서는 접근할 수 없지만, 워크플로우 파일 자체에 하드코딩된 값이 있다면 노출될 수 있으니까요.

GitHub Billing 페이지(Settings → Billing and plans → Plans and usage)에서 현재 사용량을 확인할 수 있습니다. 레포지토리별 사용량 비율도 보이기 때문에 어떤 레포지토리가 분을 많이 소비하는지 파악할 수 있어요.

Budget alerts를 설정해두면 한도에 근접할 때 이메일 알림을 받을 수 있습니다.

https://docs.github.com/en/actions/reference/limits

https://github.com/resources/insights/2026-pricing-changes-for-github-actions

위의 GitHub 공식 링크에 따르면, 2026년 1월 1일부터 GitHub은 Actions runner 가격을 최대 39% 인하했습니다. 동시에 요금 체계를 분당 요금제로 전환했어요. 대부분의 사용자(96%)는 청구서에 변동이 없거나 오히려 줄어드는 수준입니다.

다만 self-hosted runner에 대한 과금 정책은 논란이 많아 시행이 연기된 상태입니다.

앞에서 분석한 전략을 실제로 적용한 내용입니다. GitHub Actions에서 돌리던 작업 중 로컬로 옮길 수 있는 것들을 옮기고, PR Auto 플로우도 개선했어요.

기존에는 PR을 올릴 때마다 두 개의 GitHub Actions 워크플로우가 실행됐습니다.

Workflow하는 일실행 시간과금
PR Labeler브랜치/파일/사이즈 기반 라벨링~5초1분
Auto AssignPR 작성자를 assignee로 지정~5초1분

두 워크플로우 합쳐서 실제 10초짜리 작업인데 매번 2분이 과금됩니다. PR 하나당 2분이면, 한 달에 50개 PR을 올리면 그것만으로 100분이에요.

이 두 워크플로우를 삭제하고, 동일한 기능을 수행하는 로컬 스크립트로 대체했습니다.

scripts/label-pr.sh
#!/usr/bin/env bash
set -euo pipefail
 
# ─── Branch 기반 라벨 ───
BRANCH=$(gh pr view "$PR_NUMBER" --json headRefName --jq '.headRefName')
case "$BRANCH" in
  feat/*|feature/*) LABELS+=("✨ feature") ;;
  fix/*)            LABELS+=("🛠️ bugfix") ;;
  hotfix/*)         LABELS+=("🚨 hotfix") ;;
esac
 
# ─── File 기반 라벨 ───
FILES=$(gh pr view "$PR_NUMBER" --json files --jq '.files[].path')
while IFS= read -r file; do
  if [[ "$file" =~ \.mdx?$ ]]; then HAS_DOCS=true; fi
  if [[ "$file" =~ \.test\.(ts|tsx)$ ]]; then HAS_TEST=true; fi
  if [[ "$file" =~ ^\.github/ ]]; then HAS_WORKFLOW=true; fi
done <<< "$FILES"
 
# ─── Size 기반 라벨 ───
ADDITIONS=$(gh pr view "$PR_NUMBER" --json additions --jq '.additions')
DELETIONS=$(gh pr view "$PR_NUMBER" --json deletions --jq '.deletions')
TOTAL=$((ADDITIONS + DELETIONS))
 
# ─── 라벨 적용 (gh api) ───
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
LABELS_JSON=$(printf '%s\n' "${LABELS[@]}" | jq -R . | jq -sc '.')
gh api "repos/${REPO}/issues/${PR_NUMBER}/labels" \
  --method POST \
  --input - <<< "{\"labels\":${LABELS_JSON}}"
 
# ─── Auto-assign (gh api) ───
GH_USER=$(gh api user --jq '.login')
gh api "repos/${REPO}/issues/${PR_NUMBER}/assignees" \
  --method POST \
  --input - <<< "{\"assignees\":[\"${GH_USER}\"]}"

.github/labeler.yml에 정의된 규칙(브랜치명, 파일 패턴, diff 사이즈)을 그대로 bash로 옮겼어요. gh CLI로 PR 정보를 조회하고 gh api로 라벨을 적용하는 구조입니다.

주의 1: 옛날 문법에 주의하세요.

처음에는 gh pr edit --add-label을 사용했는데, GitHub Projects Classic 지원 종료로 인한 GraphQL 오류(projectCards 필드 제거)가 발생해서 REST API 직접 호출 방식으로 전환했습니다. auto-assign도 마찬가지로 gh api를 사용합니다.

주의 2: 이 방식은 PR 생성 후 local script를 수동으로 실행해야 합니다.

제가 이 방식으로 흔쾌히 바꿀 수 있었던 건 AI 반자동화 덕분이었어요. 원래도 PR의 내용 구성과 생성을 Claude Code가 하도록 했거든요. 여기에 PR 생성 이후 local script를 실행하라는 지시를 추가했을 뿐입니다. 제 노력은 여전히 0입니다.

pre-commit hook에서 이미 lint와 type-check를 실행하고 있었으니, pre-push hook에는 build만 추가했어요.

.husky/pre-push
pnpm build

이게 전부입니다. push할 때 빌드가 실패하면 push가 중단되니, GitHub Actions에서 별도로 빌드 체크를 돌릴 필요가 없어져요. Git hook 단계별로 정리하면 이렇습니다.

Hook실행 시점체크 항목
pre-commitgit commitlint, type-check
pre-pushgit pushbuild

빌드는 시간이 좀 걸리지만 push 전에 한 번만 실행되니까 개발 경험에 큰 부담은 없습니다. commit은 자주 하지만 push는 작업 단위로 하니까요.

Claude Code로 local commit들의 PR을 생성할 때, commit이 많은 경우 관심사별로 그룹핑하게 지시했습니다. 기존에는 1번 그룹 PR을 생성하고 CI가 통과할 때까지 기다렸다가 자동 merge하고 2번 그룹을 생성하는 식으로 진행했어요.

기존 플로우
cherry-pick → push → PR 생성 → CI 대기 → merge

CI 대기가 그룹당 3~5분씩 걸렸습니다. 5개 그룹이면 15~25분을 기다리는 셈이에요. 하지만 pre-push hook에서 이미 빌드가 통과됐다면 CI에서 다시 확인할 필요가 없습니다.

변경 플로우
cherry-pick → push (pre-push build) → PR 생성 → 로컬 labeler → 즉시 merge

CI 대기를 빼고, 로컬 labeler를 넣고, 바로 merge하는 구조로 바꿨어요. 그룹당 3~5분의 대기 시간이 사라졌습니다.

항목기존변경 후절약
PR Labeler1분/PR (Actions)0분 (로컬)1분/PR
Auto Assign1분/PR (Actions)0분 (로컬)1분/PR
PR 빌드 체크3~7분/PR (Actions)0분 (pre-push)3~7분/PR
Auto merge 대기3~5분/그룹 (CI)0분 (즉시)3~5분/그룹

한 달에 50개 PR 기준으로 계산하면, labeler + auto-assign만으로 100분, 빌드 체크까지 합치면 250~450분을 절약할 수 있습니다. 무료 2,000분의 12~22%에 해당하는 양이에요.

GitHub Actions 무료 2,000분은 넉넉해 보이지만, 분 단위 올림 + 병렬 job 개별 과금이라는 함정 때문에 생각보다 빠르게 소진됩니다. 특히 AI 자동화로 "작은 PR을 자주 올리는" 개발 스타일과 "job을 병렬로 쪼개는" CI 설계가 만나면 소비 속도가 가속됩니다.

병렬 job은 빠르지만 비싸고, 직렬 job은 느리지만 쌉니다. 속도와 비용을 잘 저울질 해야 합니다.

사실 2,000분을 넘어도 계속 빌드는 가능합니다. 한 달에 1만원도 안 넘을 거예요. 그럼에도 제가 이전부터 hosting에 $1라도 아끼려고 하고, 비용 절약을 하려는 이유가 있습니다.

이런 비용 최적화에 대한 노력이 모든 곳에서 도움이 되거든요. 저로서는 $1를 아끼는 방식의 차이이지만, 회사에서는 월 $100를 넘게 아끼기도 하죠. 제 개인적으로도 가속에 주춤하지 않을 수 있는 원동력이 되고요. 저는 그래서 기능 개발과 함께 비용적인 측면도 최적화를 시도해보고 있습니다.

  1. 과금 구조를 이해해야 합니다. 분 단위 올림, 병렬 job 개별 계산, OS 배율까지 알아야 정확한 비용 예측이 가능합니다.
  2. 병렬이 항상 답은 아닙니다. job이 작다면, 비용 관점에서는 직렬이 효율적이에요. pnpm install 같은 공통 작업을 한 번만 하는 것도 직렬의 장점입니다.
  3. 로컬과 CI의 역할을 분리해야 합니다. pre-commit에서 lint/type-check, pre-push에서 빌드, PR labeling도 로컬 스크립트로. Actions는 배포처럼 자동화가 꼭 필요한 곳에만 사용합니다.
  4. 로컬 배포는 빠르고 유연합니다. GitHub Actions의 7분이 로컬에서는 1분도 안 걸리고, 아무 브랜치나 배포해서 확인해볼 수 있습니다.
  5. Public 레포지토리는 무료입니다. 오픈소스 프로젝트라면 public 전환이 가장 확실한 해결책입니다.

제 경험이 이 글을 보신 분들께도 도움이 되셨길 바랍니다. 읽어주셔서 감사합니다.