본문으로 건너뛰기
최서희Frontend Engineer
← 블로그

DevOps

Docker 빌드 129초 → 21초, 모노레포 CI/CD 최적화 실전기

· 5분 읽기

PR 하나당 2분 30초 기다리던 CI를 최대 84% 단축하고, 일반적인 코드 변경 PR은 42초까지 줄인 개선 경험


1. 들어가며

인턴으로 참여한 서비스 프로젝트에서 NestJS(백엔드)와 Next.js(프론트엔드) 기반의 pnpm 모노레포 구조를 사용하고 있었습니다.

  • PR 검증: GitHub Actions
  • 실제 배포: GCP Cloud Build → Cloud Run

개발을 진행하면서 가장 크게 느낀 문제는 느린 CI 속도였습니다.

코드 한 줄 수정에도 2분 30초 이상 대기해야 했고, 이 시간이 반복되면서 개발 흐름이 끊기고 생산성이 저하되는 문제가 있었습니다.


2. 문제의 본질

기존 CI 구조는 변경 범위를 고려하지 않고 모든 작업을 수행하고 있었습니다.

  1. 코드 1줄 변경에도 Docker 전체 재빌드
  2. 의존성 변경이 없어도 pnpm install 매번 실행

즉, 불필요한 작업을 반복하는 구조였습니다.


3. 최적화 전 상황

3.1 전체 흐름

모든 PR
→ 의존성 설치
→ 백엔드 빌드
→ 프론트엔드 빌드
→ Docker 이미지 빌드 (백엔드 + 프론트)

3.2 소요 시간

워크플로시간

<table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"><tbody><tr><td>PR 검증</td><td>약 2분 30초</td></tr><tr><td>배포</td><td>약 10분</td></tr></tbody></table>


4. 핵심 최적화

4.1 조건부 Docker 빌드

대부분의 PR은 소스 코드만 변경되는데, 기존에는 매번 Docker 이미지를 빌드하고 있었습니다.

이를 개선하기 위해 특정 파일이 변경된 경우에만 Docker 빌드를 실행하도록 변경했습니다.

# 변경된 파일 기준 실행 여부 판단 (예시)
if: <infra-or-dockerfile-changed>

4.2 결과

  1. 일반적인 코드 수정 PR에서 Docker 빌드 완전 제거
  2. 약 2분 이상의 시간 절약

5. 적용한 최적화 항목

5.1 pnpm 의존성 캐싱

lockfile 기준으로 캐시 키를 생성하여 의존성이 변경되지 않으면 설치 과정을 생략하도록 개선했습니다.

- uses: actions/cache
  with:
    path: <pnpm-store-path>
    key: <cache-key-based-on-lockfile>

5.2 lint 단계 추가

빌드 이전에 코드 품질을 검증하도록 lint 단계를 추가했습니다.

pnpm install
pnpm run lint
pnpm run build

5.3 Docker BuildKit 캐시 활용

Docker 빌드 시 레이어 캐시를 재사용하도록 설정했습니다.

cache-from: <cache-source>
cache-to: <cache-destination>

5.4 배포 시 Docker 레이어 재사용

이전 이미지(latest)를 활용하여 변경된 부분만 다시 빌드하도록 개선했습니다.

docker build --cache-from <previous-image>

5.5 Dockerfile 최적화

  1. 중복된 의존성 설치 제거
  2. 불필요한 캐시 무효화 제거

6. 실측 결과

6.1 최적화 전

<table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"><tbody><tr><td>항목</td><td>시간</td></tr><tr><td>Docker (frontend)</td><td>129초</td></tr><tr><td>전체 CI</td><td>약 129초</td></tr></tbody></table>


6.2 최적화 후

<table style="border-collapse: collapse; width: 100%;" border="1" data-ke-align="alignLeft"><tbody><tr><td>항목</td><td>시간</td></tr><tr><td>Docker (frontend)</td><td>21초</td></tr><tr><td>전체 CI</td><td>약 42초</td></tr></tbody></table>


7. 개선 효과

  1. Docker 빌드: 129초 → 21초 (약 84% 단축)
  2. 일반 PR: 약 129초 → 42초 (약 67% 단축)

8. 운영하면서 느낀 점

8.1 CI 최적화의 핵심

CI 최적화에서 가장 중요한 것은 속도를 높이는 것이 아니라 불필요한 작업을 제거하는 것입니다.


8.2 캐시는 누적 효과로 봐야 한다

초기 빌드는 캐시 저장 비용으로 인해 느릴 수 있지만, 반복 실행에서는 큰 성능 개선을 제공합니다.


8.3 작은 개선의 누적

캐시, 조건 분기, Dockerfile 정리와 같은 작은 개선들이 전체 CI 속도에 큰 영향을 미쳤습니다.


9. 마무리

CI/CD는 한 번 구축하고 끝나는 것이 아니라 지속적으로 개선해야 하는 영역이라고 느꼈습니다.

이번 경험을 통해 다음을 배울 수 있었습니다.

  1. 병목을 찾는 방법
  2. 캐시 전략의 중요성
  3. 불필요한 작업 제거의 효과

비슷한 환경에서 CI가 느리다면 "이 작업이 매번 필요한가?"를 먼저 점검해보는 것을 추천드립니다.

댓글

GitHub 계정으로 댓글을 남길 수 있어요.