사내 백엔드 서비스의 GitHub Actions CI/CD 파이프라인이 매 배포마다 13분씩 걸리고 있었어요. 개발자 경험 측면에서도, 비용 측면에서도 개선이 필요했죠. 패키지 매니저를 yarn에서 Bun으로 전환하고, Docker 빌드 전략과 캐시를 최적화한 결과, CD는 10분에서 4분대로, CI를 추가하더라도 전체 파이프라인을 7분 이내로 유지할 수 있었어요.
이 글에서는 어떤 변경이 어떤 효과를 가져왔는지, 캐시 히트율은 어떻게 관리했는지를 정리해볼게요.

| 단계 | Before (yarn) | After (Bun + 최적화) | 변화 |
|---|---|---|---|
| 전체 파이프라인 | ~13분 | ~6분 39초 | -49% |
| Deploy Job | ~10분 22초 | ~4분 17초 | -59% |
| Test Gate | ~2분 35초 | ~2분 19초 | -10% |
| Billable Minutes | ~23분/배포 | ~16분/배포 | -30% |
캐시가 없는 cold build 기준의 수치예요. 캐시 적용 후에는 전체 5분 이하까지 가능해졌어요.
가장 먼저 한 일은 패키지 매니저를 yarn에서 Bun으로 교체한 거예요.
bun install이 yarn 대비 2~3배 빨라요기존에는 2-stage로 순차 빌드했어요:
# Before: 2-stage 순차 빌드
FROM node:20-alpine
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# ... build ...
FROM node:20-alpine
RUN yarn install --production --frozen-lockfile
COPY --from=0 /app/dist ./dist이걸 3-stage 병렬 빌드로 바꿨어요:
# After: 3-stage 병렬 빌드
FROM node:20-alpine AS builder # Stage 1: 소스 빌드
FROM oven/bun:1-alpine AS prod-deps # Stage 2: 프로덕션 deps (병렬)
FROM node:20-alpine # Stage 3: 최종 런타임핵심은 BuildKit이 Stage 1과 Stage 2를 동시에 실행한다는 점이에요. 소스를 빌드하는 동안 프로덕션 의존성 설치가 병렬로 진행되므로, 전체 Docker 빌드 시간이 크게 줄어들어요.
이 변경만으로 전체 파이프라인이 13분에서 10분으로 감소했어요.

Bun 전환과 함께 GitHub Actions 워크플로우 자체도 손봤어요.
이전에는 deprecated된 setup-gcloud@v0.2.0을 사용하고 있었어요. 이 Action은 매 빌드마다 gcloud SDK를 통째로 다운로드하고 설치했기 때문에 일관되게 3분이 소요됐죠.
최신 버전으로 교체한 결과:
auth@v2: 서비스 계정으로 인증만 수행 (~1초)setup-gcloud@v2: 러너에 사전 설치된 SDK를 활용, 프로젝트 설정만 (~26초)배포 명령에서도 불필요한 부분을 제거했어요:
gcloud components install beta 를 실행하던 것을 제거gcloud beta run deploy 대신 GA 명령어 gcloud run deploy 를 직접 사용1분 56초 → 48초로 단축.
| 요인 | 절감 시간 | 비율 |
|---|---|---|
| Docker Buildx + Bun Dockerfile | 2분 49초 | 44% |
| GCloud 인증 업그레이드 | 2분 34초 | 40% |
| Cloud Run Deploy 최적화 | 1분 08초 | 16% |

Bun 전환과 CI 업그레이드로 cold build 성능은 확보했어요. 하지만 진짜 일상적인 배포 속도를 끌어올리려면 캐시 전략이 핵심이었어요.
1. Bun 패키지 캐시 (--mount=type=cache)
Dockerfile 내에서 RUN --mount=type=cache 를 활용해 Bun의 패키지 다운로드 캐시를 빌드 간에 재사용하도록 했어요. package.json이 변경되지 않는 한 의존성 설치를 거의 스킵할 수 있어요.
2. GitHub Actions 레이어 캐시
docker/build-push-action의 cache-from과 cache-to 옵션으로 GHA 캐시 백엔드를 연결했어요. 이전 빌드의 Docker 레이어를 다음 빌드에서 재활용해요.
- uses: docker/build-push-action@v5
with:
push: true
tags: $ env.IMAGE
cache-from: type=gha # 이전 빌드의 레이어 캐시 로드
cache-to: type=gha,mode=max # 모든 레이어를 캐시에 저장3. 캐시 히트 시 스킵되는 주요 레이어
캐시가 히트하면 다음 작업들이 통째로 스킵돼요:
apk add): ~37초bun, pm2): ~32초bun install --production): ~29초모든 레이어가 매번 캐시되는 것은 아니에요. 코드만 변경된 배포에서는 대부분의 레이어가 캐시 히트하지만, 의존성 변경이나 시스템 패키지 업데이트가 포함되면 히트율이 떨어져요.
운영 환경에서 측정한 평균 캐시 히트율은 약 46%였어요. 이 수치는 "항상 빠르다"와 "가끔 빠르다" 사이의 현실적인 지점인데, 실제로 체감 배포 속도를 일관되게 7분 이내로 유지하는 데 충분했어요.
COPY package.json → RUN install → COPY src/ 순서를 지키는 것만으로도 히트율이 크게 올라가요.Before (yarn) ████████████████████████████████████████ ~13분
Bun 전환 ██████████████████████████████ ~10분 (-22%)
Bun + CI 업그레이드 ████████████████████ ~6분 39초 (-49%)
캐시 적용 후 ██████████████ ~5분 이하 (체감 ~4분대)Bun 전환은 패키지 매니저 교체만으로 끝나지 않았어요. 여러 프로젝트(backend, 캘린더, 동행 어드민, 넷플연가 어드민 등)를 순차 전환하면서 다양한 호환성 이슈를 만났고, 이를 해결하는 과정에서 단계적 마이그레이션 전략의 중요성을 체감했어요.
가장 작은 프로젝트(캘린더 서비스)부터 시작했어요. 진행 순서는 다음과 같아요:
package.json scripts를 bun으로 변경bun install로 의존성 설치 테스트파일럿에서 검증한 패턴을 나머지 프로젝트에 순차 적용하는 방식이었어요.
전환 과정에서 만난 이슈들과 해결 방법이에요:
① native addon 호환성
bcrypt 등)이 Bun에서 동작하지 않음bcryptjs)로 교체하거나 Bun의 FFI 활용② 환경 변수 로딩
dotenv 없이도 Bun이 .env를 자동 로딩하면서 기존 로직과 충돌.env 로딩을 활용하도록 통일③ TypeScript 경로 별칭
tsconfig.json의 paths 설정이 런타임에서 다르게 해석되는 케이스bunfig.toml에서 명시적으로 경로 매핑 설정호환성 이슈를 겪으면서 "전환 과정에서 회귀를 방지할 안전장치가 필요하다"는 것을 절감했어요. GitHub Actions에 다음 파이프라인을 구축했어요:
# PR이 열리면 자동 실행
on:
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: oven-sh/setup-bun@v1
- run: bun install
- run: bun test # 단위 테스트
- run: bun run typecheck # 타입 체크
- run: bun run lint # 린트
- run: bun run build # 빌드 검증핵심 원칙은 세 가지였어요:
이 파이프라인 덕분에 전체 프로젝트로 전환을 확대하는 과정에서 배포 후 장애 0건을 달성할 수 있었어요.
이번 개선에서 인상적이었던 점은, 대부분의 성능 향상이 새로운 인프라 도입이 아니라 기존 도구의 버전 업그레이드와 올바른 설정에서 나왔다는 거예요.
화려한 기술 스택 변경보다, 자신의 파이프라인을 한 번 꼼꼼히 들여다보는 것이 더 효과적일 수 있어요. 한 단계씩 프로파일링하면 "저 매달린 열매"가 생각보다 많이 보여요.
Bun은 패키지 매니저로서 빠르고 안정적이었고, Docker BuildKit의 병렬 빌드와 캐시 마운트는 그 속도를 더 극대화해줬어요. CI/CD가 느려서 고민이라면, 런타임 전환과 캐시 전략을 함께 검토해보시길 추천해요.