빌드가 빨랐다 느렸다 하는 이유. 명령 종류마다 판정 기준이 다르다.
-
COPY · ADD — 파일 메타데이터로 체크섬을 계산해 비교한다
- mtime(수정 시각)은 계산에 넣지 않는다
- touch 만 해서는 캐시가 깨지지 않는다
-
RUN — '명령 문자열' 만 본다
- RUN apt-get update 로 실제 무엇이 바뀌었는지는 보지 않는다
- 문자열이 같으면 몇 달 전 캐시를 그대로 쓴다
-
그리고 캐시가 한 번 깨지면 그 아래 모든 레이어가 다시 실행된다
그래서 순서가 전부다
# ✗ 소스가 한 줄만 바뀌어도 의존성을 다시 받는다
COPY . .
RUN npm ci
# ✓ 의존성 목록이 바뀔 때만 다시 받는다
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
원칙 — 자주 바뀌는 것을 뒤에 둔다
| 거의 안 바뀜 | 베이스 이미지 · 시스템 패키지 | |
|---|---|---|
| 가끔 바뀜 | 의존성 목록 (package.json · pom.xml · go.mod) | |
| 자주 바뀜 | 애플리케이션 소스 |
apt-get update 의 함정
# ✗ update 가 캐시되면 낡은 인덱스로 install 한다
RUN apt-get update
RUN apt-get install -y curl
# ✓ 한 레이어로 묶는다
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
update 와 install 이 다른 레이어면
- install 줄만 바뀔 때 update 는 캐시를 쓴다
- 몇 달 전 패키지 인덱스로 설치를 시도한다 → 404 나 낡은 버전
정리(rm -rf)도 같은 레이어에서 해야 크기가 준다
캐시 마운트 — 레이어에 굽지 않는다
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.m2 \
./mvnw -B package -DskipTests
의존성 캐시를 '빌드 사이에 유지되는 별도 저장소' 에 둔다
- 이미지가 커지지 않는다
- COPY 순서로 캐시를 지켜야 하는 부담이 줄어든다
대상 예 — /root/.m2 · /root/.gradle · ~/.npm · /go/pkg/mod · ~/.cache/pip
CI 에서 캐시가 안 먹을 때
-
러너가 매번 새 머신이면 로컬 캐시가 없다
-
레지스트리 캐시를 쓴다
- docker buildx build \
- --cache-from type=registry,ref=myrepo/cache \
- --cache-to type=registry,ref=myrepo/cache,mode=max
- docker buildx build \
또 하나 — 빌드 컨텍스트에 매번 바뀌는 파일이 들어가면 캐시가 깨진다 (타임스탬프 파일 · 빌드 번호) → .dockerignore 로 제외
캐시가 깨졌는지 확인하는 법
BuildKit 은 각 단계에 CACHED 를 표시한다
#8 [4/6] RUN npm ci
#8 CACHED ← 캐시 적중
#9 [5/6] COPY . .
#9 DONE 0.3s ← 여기서 깨졌다
어느 단계부터 CACHED 가 사라지는지 보면 원인이 바로 나온다 빌드가 느릴 때 로그를 먼저 읽는 이유다
캐시가 오히려 문제가 될 때
-
베이스 이미지의 보안 패치가 반영되지 않는다
- FROM 은 태그가 같으면 로컬 캐시를 쓴다
- 주기적으로 docker build --pull 로 베이스를 다시 받는다
- CI 는 --pull 을 기본으로 두는 편이 안전하다
-
캐시가 디스크를 먹는다
- docker system df -v 의 Build Cache 항목
- docker builder prune --filter until=168h
면접 함정
- ❌ "파일을 안 바꿨으니 캐시가 유지된다" → 앞 레이어가 깨지면 아래는 전부 재실행이다.
- ❌ "RUN도 결과를 비교한다" → 명령 문자열만 본다. 그래서
apt-get update가 낡는다.