CI 러너는 매 실행마다 깨끗한 환경에서 시작하기 때문에, 로컬 빌드 캐시가 없습니다. 그래서 같은 Dockerfile인데도 매번 npm ci부터 전부 다시 돌아 빌드가 5분, 10분씩 걸립니다. docker buildx의 레지스트리 캐시를 쓰면 빌드 레이어를 레지스트리에 저장해두고 다음 빌드에서 끌어와 재사용할 수 있습니다.
왜 레지스트리에 저장하나
CI 러너 간에 공유할 수 있는 영속 저장소가 레지스트리뿐인 경우가 많습니다. 캐시를 이미지처럼 push/pull하면 러너가 매번 바뀌어도 캐시가 살아남습니다.
| 캐시 백엔드 | 저장 위치 | 적합한 상황 |
|---|---|---|
inline | 이미지 안에 캐시 메타 포함 | 간단하지만 mode=max 불가 |
registry | 별도 캐시 태그로 레지스트리에 push | CI에서 러너 간 공유, 권장 |
local | 디스크 경로 | 캐시를 직접 마운트하는 셀프호스트 러너 |
기본 명령
--cache-to로 이번 빌드의 레이어를 캐시로 밀어 올리고, --cache-from으로 다음 빌드가 그걸 끌어옵니다.
docker buildx build \
--cache-to type=registry,ref=myrepo/app:buildcache,mode=max \
--cache-from type=registry,ref=myrepo/app:buildcache \
-t myrepo/app:latest \
--push .
여기서 mode=max가 중요합니다. 기본값 min은 최종 이미지에 남는 레이어만 캐시하지만, max는 멀티스테이지의 중간 빌드 단계까지 모두 캐시합니다. 빌드 스테이지가 분리된 경우 max라야 npm ci 같은 중간 단계가 캐시됩니다.
CI 첫 실행 보호
캐시 태그가 아직 없는 첫 빌드에서는 --cache-from이 404로 실패할 수 있습니다. 캐시가 없어도 빌드는 진행되도록 ignore-error를 줍니다.
--cache-from type=registry,ref=myrepo/app:buildcache,ignore-error=true
캐시가 먹는지 확인
빌드 로그에서 단계마다 CACHED가 보이면 적중한 것입니다.
=> CACHED [build 3/6] RUN npm ci
=> CACHED [build 4/6] COPY . .
=> [build 5/6] RUN npm run build
CACHED가 하나도 안 뜬다면, 레이어 순서가 잘못됐거나(자주 바뀌는 파일을 먼저 COPY) mode=max를 빠뜨린 경우입니다. 자주 안 바뀌는 의존성 설치를 소스 COPY보다 앞에 두는 것이 캐시 적중의 전제입니다.
체크리스트
# 1. buildx 빌더가 docker-container 드라이버인지 (기본 드라이버는 registry 캐시 미지원)
docker buildx inspect --bootstrap
# 2. 캐시 태그 push/pull 권한이 있는 레지스트리인지 확인
# 3. mode=max로 중간 스테이지까지 캐시
docker buildx build --cache-to type=registry,ref=...,mode=max ...
기본 docker 드라이버 빌더는 registry 캐시를 지원하지 않습니다. docker buildx create --use로 docker-container 드라이버 빌더를 만들어 쓰는 게 첫 단추입니다.
buildx 빌더를 만들고 레지스트리 캐시로 두 번째 빌드가 얼마나 빨라지는지 직접 측정하는 실습은 도커 트랙에서 회원가입 없이 무료로 해볼 수 있습니다.