IT/CI CD

BuildKit cache mount, CI에서도 진짜 캐시 되게 하기

gfrog 2026. 8. 13. 15:14

 

CI에서 Docker 빌드 시간이 아깝다는 얘기를 최근에도 팀에서 몇 번 들었다. 로컬에선 5초 만에 끝나는데 GitHub Actions에서만 3분씩 걸리는, 그 짜증나는 상황. 오늘 알게 된 건 아니지만 의외로 모르는 분들 꽤 많더라. 짧게 정리해둔다.

문제는 러너가 매번 새거라는 점

BuildKit의 RUN --mount=type=cache는 원래 로컬에서 잘 돈다. 예를 들면 이런 식.

# syntax=docker/dockerfile:1.7
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
    npm ci
COPY . .
RUN --mount=type=cache,target=/app/node_modules/.cache \
    npm run build

로컬에선 .npm이랑 webpack/vite 캐시가 남아 있어서 두 번째 빌드부터 눈에 띄게 빨라진다. 근데 GitHub Actions 러너는 매번 초기화되니까 cache mount도 결국 비어있는 상태로 시작한다. 이게 함정.

해결: cache-from/to 를 붙여준다

캐시 mount 자체를 registry나 GitHub Actions 캐시에 밀어넣는 방식이다.

- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/org/app:latest
    cache-from: type=gha
    cache-to: type=gha,mode=max

mode=max가 중요하다. 기본값(mode=min)은 최종 스테이지만 캐시하는데, cache mount 내용은 중간 레이어에 붙어 있어서 min으론 안 올라간다. 이걸 몰라서 "왜 cache-to 붙였는데도 안 빨라지지?" 하는 경우가 대부분이다. 최근 6개월 동안 팀에서 두 번 봤다.

GHA cache 용량 주의

type=gha는 10GB 제한이 있다. 이미지 여러 개 굴는 monorepo면 금방 찬다. 그때는 scope를 나눈다.

cache-from: type=gha,scope=api
cache-to: type=gha,mode=max,scope=api

서비스별로 scope를 다르게 주면 서로 밀어내지 않는다. 아니면 아예 registry 캐시(type=registry,ref=...:buildcache)로 옮기는 것도 방법인데, ECR/GHCR 스토리지 비용이랑 저울질해야 한다.

결론 아닌 결론

정리하면 세 줄:

  • cache mount는 로컬에서만 도는 게 기본이다
  • mode=max 안 붙이면 CI에선 반쪽짜리다
  • 용량 차면 scope로 쪼갠다

이거만 알아도 CI 빌드 시간이 절반 이하로 떨어지는 경우가 많다. 우리 팀은 프론트 빌드가 4분 → 40초 정도로 줄었다. 참고로 요즘 나오는 여러 벤치마크 글에서도 잘 튜닝하면 80% 넘게 단축 가능하다고 하는데, 우리 환경은 그 정도까지는 아니었다. 근데 40초는 확실히 참을 만 하다.