TechFeedTechFeed
Cloud & DevOps

GitHub Actions CI/CD 비용이 월 $2,000을 넘었을 때 — 워크플로우 최적화로 80% 절감한 사례

모노레포 스타트업이 GitHub Actions 월 $2,100 비용을 $380으로 줄인 3단계 최적화 사례. 캐싱, E2E 분할, 셀프호스트 러너 도입 과정의 시도와 실패를 시간순으로 정리했다.

GitHub Actions 사용량이 늘면서 월 CI/CD 비용이 $2,000을 넘긴 5인 스타트업이 있었다. 빌드 시간은 느려지고 비용은 계속 올라갔다. 이 팀은 캐싱 전략 재설계, 매트릭스 최적화, 셀프호스트 러너 도입이라는 3단계 접근으로 월 비용을 $380까지 줄였다. 이 글은 그 과정에서 시도한 것, 실패한 것, 결국 효과가 있었던 것을 시간순으로 정리한 사례 기록이다.

※ 이 글은 2026년 3월 기준, GitHub Actions 공식 문서 및 실제 워크플로우 로그 기반으로 작성됐습니다. 팀 정보는 익명 처리되었습니다.

월 $2,000 청구서를 받고 나서야 알게 된 것들

이 팀은 Next.js 모노레포를 운영하는 B2B SaaS 스타트업이다. 프론트엔드 2개, 백엔드 API 1개, 공유 패키지 3개로 구성된 Turborepo 모노레포였다. 개발자 5명이 하루 평균 15~20개 PR을 올렸고, PR마다 린트·타입체크·유닛테스트·E2E 테스트·프리뷰 배포가 돌아갔다.

문제를 인지한 건 2025년 11월이었다. GitHub 결제 페이지에서 Actions 사용량이 월 12,000분을 넘긴 걸 확인했다. GitHub Team 플랜의 무료 포함 분(3,000분)을 4배 초과한 수치였다. 초과분에 대해 Linux 러너 분당 $0.008이 부과되어 월 $2,100이 찍혔다.

비용 구조를 분석하자 원인이 드러났다. 전체 Actions 사용 시간의 68%가 E2E 테스트에서 나왔다. Playwright E2E 테스트가 PR마다 풀 스위트로 돌아가고 있었고, 한 번 실행에 평균 18분이 걸렸다. 나머지 32% 중 절반은 의존성 설치(npm install)였다.

팀 리드가 처음 한 말: "우리가 돈을 내고 있는 게 테스트가 아니라 npm install이었다." 실제로 캐시 히트율을 확인해보니 12%에 불과했다. 캐시 키가 잘못 설정되어 거의 매번 의존성을 처음부터 설치하고 있었다.

GitHub Actions 사용량 대시보드 — 월별 사용 분 추이와 비용 그래프
월 12,000분을 넘기면 비용이 급격히 올라간다 — GitHub Actions 사용량 대시보드 예시 (출처: GitHub Docs)

1단계 — 캐싱을 제대로 고치자

가장 먼저 손댄 건 캐싱이었다. 기존 워크플로우는 actions/cache@v3를 쓰고 있었는데, 캐시 키가 node-modules-${{ hashFiles('package-lock.json') }}로 되어 있었다. 문제는 모노레포라서 어떤 패키지든 하나만 바뀌어도 전체 캐시가 무효화된다는 것이었다.

기존 캐시 설정 — 모노레포에서 캐시 히트율 12%의 원인
- uses: actions/cache@v3 with: path: node_modules key: node-modules-${{ hashFiles('package-lock.json') }}

이걸 패키지별 해시 + 복원 키 체인으로 바꿨다. actions/setup-node@v4의 내장 캐시를 사용하고, restore-keys로 부분 매칭을 허용했다. 추가로 Turborepo의 리모트 캐시를 활성화했다. Vercel의 무료 Turbo Remote Cache를 연결하면 빌드 결과물이 팀원 간에 공유된다.

개선된 캐시 설정 — setup-node 내장 캐시 + restore-keys
- uses: actions/setup-node@v4 with: node-version: 20 cache: 'npm' - name: Turbo Cache uses: actions/cache@v4 with: path: .turbo key: turbo-${{ github.ref }}-${{ github.sha }} restore-keys: | turbo-${{ github.ref }}- turbo-

결과: 캐시 히트율이 12%에서 87%로 올라갔다. npm install 시간이 평균 3분 20초에서 25초로 줄었다. 이것만으로 월 Actions 사용 시간이 12,000분 → 8,400분으로 30% 감소했다. 하지만 여전히 비용은 월 $1,400 수준이었다.

⚠️ 실패한 시도: 처음에 node_modules 전체를 캐시하려고 했는데, 모노레포 환경에서 node_modules 캐시는 심볼릭 링크 문제로 깨지는 경우가 잦았다. npm의 글로벌 캐시(~/.npm)를 캐시하는 게 더 안정적이다. setup-node의 내장 캐시가 이 방식을 쓴다.

2단계 — E2E 테스트를 분할하고 조건부로 실행하기

비용의 68%를 차지하던 E2E 테스트를 손봤다. 기존에는 PR마다 전체 Playwright 테스트 스위트(약 120개 테스트)가 돌아갔다. 모노레포의 어떤 파일이 바뀌든 전체 E2E가 실행됐다.

두 가지를 바꿨다.

첫째, 변경 감지 기반 조건부 실행. dorny/paths-filter@v3를 써서 변경된 패키지만 식별하고, 해당 패키지에 관련된 E2E만 돌렸다. 프론트엔드 A만 바뀌면 프론트엔드 A의 E2E만 실행되는 식이다.

변경 감지 기반 E2E 조건부 실행
changes: runs-on: ubuntu-latest outputs: frontend-a: ${{ steps.filter.outputs.frontend-a }} frontend-b: ${{ steps.filter.outputs.frontend-b }} api: ${{ steps.filter.outputs.api }} steps: - uses: dorny/paths-filter@v3 id: filter with: filters: | frontend-a: - 'apps/frontend-a/**' - 'packages/shared-ui/**' frontend-b: - 'apps/frontend-b/**' - 'packages/shared-ui/**' api: - 'apps/api/**' - 'packages/shared-utils/**' e2e-frontend-a: needs: changes if: ${{ needs.changes.outputs.frontend-a == 'true' }} runs-on: ubuntu-latest steps: - run: npx playwright test --project=frontend-a

둘째, Playwright 샤딩. 전체 E2E가 돌아야 하는 경우(main 브랜치 머지, 공유 패키지 변경)에는 --shard 옵션으로 4개 러너에 분산 실행했다. 단일 러너에서 18분 걸리던 게 4개 샤드에서 5분으로 줄었다.

Playwright 샤딩 — 4개 러너에 테스트 분산
e2e-full: strategy: matrix: shard: [1/4, 2/4, 3/4, 4/4] runs-on: ubuntu-latest steps: - run: npx playwright test --shard=${{ matrix.shard }}
GitHub Actions 매트릭스 전략으로 E2E 테스트를 4개 러너에 샤딩한 워크플로우 실행 화면
샤딩 적용 후 E2E 실행 시간이 18분에서 5분으로 단축된 워크플로우 (출처: GitHub Actions UI)

결과: E2E 테스트의 월간 사용 시간이 8,160분에서 2,100분으로 74% 줄었다. 전체 월간 사용 시간이 8,400분에서 4,200분으로 절반이 됐고, 비용은 $1,400에서 $640으로 내려왔다.

⚠️ 실패한 시도: 처음에 paths-filter 없이 git diff로 직접 변경 파일을 파싱하려고 했다. PR이 여러 커밋으로 구성된 경우 base 브랜치 비교가 꼬이는 문제가 생겨서 포기했다. 전용 액션을 쓰는 게 훨씬 안정적이다.

3단계 — 셀프호스트 러너로 전환한 이유와 방법

$640까지 줄었지만, 팀은 더 줄이고 싶었다. E2E 테스트가 여전히 월 2,100분을 쓰고 있었고, 이 부분은 GitHub 호스트 러너에서 더 최적화하기 어려웠다. 그래서 E2E 전용 셀프호스트 러너를 도입하기로 했다.

선택한 도구는 actions-runner-controller(ARC)다. 팀이 이미 운영 중이던 AWS EKS 클러스터에 ARC를 배포하고, E2E 전용 러너 스케일셋을 구성했다. Spot 인스턴스(c6i.xlarge)를 써서 비용을 최소화했다.

ARC 러너 스케일셋 Helm values — Spot 인스턴스 + 자동 스케일링
# values.yaml githubConfigUrl: https://github.com/org/monorepo githubConfigSecret: github-runner-secret minRunners: 0 maxRunners: 6 containerMode: type: dind template: spec: nodeSelector: node.kubernetes.io/lifecycle: spot containers: - name: runner resources: requests: cpu: "2" memory: "4Gi" limits: cpu: "4" memory: "8Gi"

셀프호스트 러너의 가장 큰 장점은 디스크 캐시가 러너에 남는다는 것이다. GitHub 호스트 러너는 매번 새 VM이라 Playwright 브라우저 바이너리를 매번 다운로드한다(약 200MB). 셀프호스트 러너에서는 이 비용이 0이다. Playwright 실행 시간이 5분에서 3분으로 추가 단축됐다.

AWS 비용은 어떻게 됐을까? Spot c6i.xlarge(4 vCPU, 8 GiB)의 시간당 비용이 약 $0.05다. 하루 평균 러너 가동 시간이 2시간이고, 월 22영업일 기준으로 약 $130/월이다. 여기에 EBS 스토리지와 데이터 전송료를 합치면 월 $160 정도였다.

E2E를 셀프호스트로 옮기면서 GitHub Actions 과금 대상 사용 시간은 4,200분에서 2,100분으로 줄었다. 무료 포함분(3,000분) 안에 들어왔기 때문에 GitHub 추가 과금은 $0이 됐다. AWS 러너 비용 $160과 기존 린트·타입체크·유닛테스트의 GitHub Actions 비용 $220을 합치면 총 $380/월이다.

⚠️ 주의할 점: 셀프호스트 러너에서 Docker-in-Docker(dind)를 쓰면 보안 이슈가 있다. 퍼블릭 리포지토리에서는 셀프호스트 러너를 쓰지 말라는 게 GitHub의 공식 권고다. 이 팀은 프라이빗 리포지토리여서 가능했다. 퍼블릭 리포지토리라면 larger runners 옵션을 검토하는 게 낫다.

4개월 비용 변화 타임라인

$2,100에서 $380으로, 82% 절감이다. 가장 효과가 컸던 단계는 2단계(E2E 분할)로 단독으로 54% 절감 효과를 냈다. 캐싱 개선은 30%, 셀프호스트 전환은 추가로 41%를 줄였다.

CI/CD 비용 최적화 3단계 타임라인 — 캐싱, 매트릭스, 셀프호스트 러너
3단계 최적화의 누적 절감 효과 — 각 단계별 비용 변화 (팀 내부 자료 기반 재구성)

이 과정에서 배운 5가지

1. 캐시 히트율을 먼저 확인하라. GitHub Actions의 캐시 히트율은 워크플로우 로그에서 확인할 수 있다. Cache hit 또는 Cache miss가 로그에 남는다. 이 팀은 3개월 동안 캐시가 거의 작동하지 않고 있다는 걸 몰랐다.

2. 모노레포에서는 경로 기반 조건 실행이 필수다. 모노레포의 모든 PR에서 모든 테스트를 돌리면, 팀 규모가 커질수록 비용이 선형이 아니라 지수적으로 늘어난다.

3. E2E는 가장 비싼 테스트다. 유닛테스트 1,000개의 실행 시간이 E2E 10개보다 짧다. E2E를 최적화하면 전체 CI/CD 비용의 절반 이상을 절약할 수 있다.

4. 셀프호스트 러너는 만능이 아니다. 관리 부담이 생긴다. ARC 업데이트, Spot 인스턴스 중단 대응, 러너 이미지 유지보수가 필요하다. 이 팀은 DevOps 경험이 있는 개발자가 있어서 가능했다. 없다면 GitHub의 larger runners를 쓰는 게 낫다. 4x CPU 러너는 분당 $0.032로 기본 러너($0.008)의 4배지만, 병렬 처리로 실행 시간이 1/3로 줄면 오히려 이득이다.

5. 측정 없이 최적화하지 마라. 이 팀이 처음 "CI가 느리다"고 느꼈을 때 바로 셀프호스트 러너를 검토했다. 하지만 측정해보니 병목은 캐시 미스였다. 비용과 시간 분포를 먼저 파악하고, 가장 큰 부분부터 손대야 한다.

💡 팁: GitHub의 gh run list --json usage 명령어로 워크플로우별 사용 시간을 JSON으로 뽑을 수 있다. 스프레드시트에 넣으면 어떤 워크플로우가 비용을 가장 많이 잡아먹는지 한눈에 보인다.

당신의 팀은 어느 단계부터 시작해야 하나

모든 팀이 3단계를 전부 밟을 필요는 없다. 현재 상황에 따라 진입점이 다르다.

  • 월 $500 이하: 캐싱만 제대로 해도 충분하다. setup-node 내장 캐시와 restore-keys를 확인하라.
  • 월 $500~$1,500: E2E 테스트 비중을 확인하라. 50% 이상이면 조건부 실행과 샤딩이 효과적이다.
  • 월 $1,500 이상: 셀프호스트 러너를 검토할 시점이다. 단, DevOps 역량이 팀 내에 있는지 먼저 확인하라.

GitHub는 2026년 1월부터 Actions Usage Report 기능을 GA로 제공한다. 조직 설정 → Actions → Usage에서 워크플로우별·리포별 사용량을 대시보드로 볼 수 있다. 최적화를 시작하기 전에 여기서 현황을 먼저 파악하는 걸 권한다.

github-actionsci-cdcost-optimizationdevopsmonorepoplaywrightself-hosted-runnerturborepo캐싱

관련 포스트

GitHub Actions CI/CD 실전 가이드2026-02-22개발자를 위한 Docker 실전 가이드 20262026-02-19Supabase vs Firebase vs PlanetScale 비용 비교 2026 — MAU별 실비용·기능·선택 기준2026-04-22AWS vs GCP vs Azure 무료 한도 & 실비용 비교 2026 — 프리티어 졸업 후 월 얼마?2026-04-20