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%에 불과했다. 캐시 키가 잘못 설정되어 거의 매번 의존성을 처음부터 설치하고 있었다.
월 12,000분을 넘기면 비용이 급격히 올라간다 — GitHub Actions 사용량 대시보드 예시 (출처: GitHub Docs)
1단계 — 캐싱을 제대로 고치자
가장 먼저 손댄 건 캐싱이었다. 기존 워크플로우는 actions/cache@v3를 쓰고 있었는데, 캐시 키가 node-modules-${{ hashFiles('package-lock.json') }}로 되어 있었다. 문제는 모노레포라서 어떤 패키지든 하나만 바뀌어도 전체 캐시가 무효화된다는 것이었다.
이걸 패키지별 해시 + 복원 키 체인으로 바꿨다. actions/setup-node@v4의 내장 캐시를 사용하고, restore-keys로 부분 매칭을 허용했다. 추가로 Turborepo의 리모트 캐시를 활성화했다. Vercel의 무료 Turbo Remote Cache를 연결하면 빌드 결과물이 팀원 간에 공유된다.
결과: 캐시 히트율이 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만 실행되는 식이다.
셀프호스트 러너의 가장 큰 장점은 디스크 캐시가 러너에 남는다는 것이다. 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%를 줄였다.
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에서 워크플로우별·리포별 사용량을 대시보드로 볼 수 있다. 최적화를 시작하기 전에 여기서 현황을 먼저 파악하는 걸 권한다.