- Published on
Docker 빌드 캐시가 무효화되는 원인 7가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버/CI에서 docker build가 갑자기 느려졌다면, 대개는 캐시가 무효화(invalidate) 되어 매 레이어를 다시 만들고 있기 때문입니다. 문제는 “코드 조금 바꿨을 뿐인데 왜 패키지 설치부터 다시 하지?” 같은 상황이 자주 발생한다는 점이죠.
Docker의 레이어 캐시는 크게 다음 규칙으로 동작합니다.
- 각 Dockerfile 명령(
FROM,RUN,COPY,ADD등)은 레이어를 만든다. - 어떤 레이어가 캐시 히트하려면 그 레이어를 만들었던 조건(명령/입력 파일/메타데이터 등)이 동일해야 한다.
- 한 레이어가 캐시 미스가 나면, 그 아래(뒤)의 모든 레이어도 연쇄적으로 새로 빌드된다.
아래는 실무에서 가장 자주 만나는 캐시 무효화 원인 7가지와 해결 패턴입니다.
1) COPY . .가 너무 이른 위치에 있다
가장 흔한 캐시 파괴 패턴입니다. 소스 전체를 먼저 복사하면, 코드/README/테스트/로그 등 사소한 변경도 COPY 레이어를 바꾸고, 그 뒤의 RUN npm ci, RUN pip install 같은 무거운 레이어가 전부 재실행됩니다.
나쁜 예
FROM node:20
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
개선 예(의존성 파일을 먼저 복사)
FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
핵심: 변경 빈도가 낮은 것(락파일/모듈 정의)을 먼저, 변경 빈도가 높은 것(소스)을 나중에.
2) .dockerignore가 부실해 빌드 컨텍스트가 자주 바뀐다
Docker는 빌드할 때 “컨텍스트(context)”를 데몬/빌더로 전송합니다. 이 컨텍스트에 불필요한 파일이 섞이면, 예를 들어 다음과 같은 것들이 COPY 입력에 포함되어 캐시가 깨집니다.
node_modules/,dist/,.pytest_cache/,.venv/.git/(특히 훅/로그/인덱스 변경)- 로컬 로그, 임시 파일
권장 .dockerignore
.git
node_modules
dist
build
*.log
.DS_Store
.pytest_cache
.venv
팁: “이미지에는 필요 없지만 로컬에는 존재하는 폴더”는 거의 다 .dockerignore 후보입니다.
3) RUN apt-get update 같은 비결정적(Non-deterministic) 단계
apt-get update는 실행 시점의 미러 상태에 따라 결과가 바뀔 수 있습니다. 또한 아래처럼 작성하면 캐시 재사용이 꼬이거나(혹은 반대로 보안 업데이트가 반영되지 않는) 문제가 생깁니다.
흔한 패턴(권장되지 않음)
RUN apt-get update
RUN apt-get install -y curl
레이어가 분리되면 apt-get update 결과와 install이 분리되어, 캐시가 어긋나거나 오래된 인덱스를 참조할 수 있습니다.
개선 패턴(한 레이어로 결합 + 정리)
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
추가 팁: 더 강한 재현성을 원하면 Debian snapshot, 고정 버전 설치, 또는 베이스 이미지 고정(다음 항목)을 같이 고려하세요.
4) 베이스 이미지 태그가 떠다닌다(FROM ubuntu:latest)
latest, stable, alpine:3 같은 가변 태그는 시간이 지나면 동일 Dockerfile이라도 베이스가 바뀝니다. 베이스가 바뀌는 순간, 모든 레이어 캐시가 무효화됩니다.
나쁜 예
FROM ubuntu:latest
개선 예(버전 고정 또는 digest pinning)
FROM ubuntu:22.04
더 엄격히 하려면 digest로 고정합니다.
FROM ubuntu@sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
**CI에서 “어제는 빨랐는데 오늘은 전체 재빌드”**가 발생하면 가장 먼저 FROM 태그 변동을 의심하세요.
5) ARG/ENV 값이 바뀌어 레이어 키가 달라진다
ARG나 ENV는 해당 값이 사용되는 시점 이후 레이어 캐시에 영향을 줍니다. 대표적으로 CI에서 커밋 SHA를 넣거나, 빌드 시각을 넣는 경우가 그렇습니다.
캐시를 깨는 예(의도치 않게 매번 새 빌드)
ARG BUILD_SHA
ENV BUILD_SHA=$BUILD_SHA
RUN echo "build=$BUILD_SHA" > /app/build-info.txt
빌드할 때마다 --build-arg BUILD_SHA=$(git rev-parse HEAD)가 바뀌면 해당 레이어부터 캐시가 깨집니다.
개선 전략
- 정말 필요한 마지막 단계에서만
ARG/ENV를 사용해 영향을 최소화 - 빌드 메타데이터는 이미지 레이어가 아니라 라벨(label) 로 이동
ARG BUILD_SHA
LABEL org.opencontainers.image.revision=$BUILD_SHA
6) ADD의 자동 동작(압축 해제/원격 URL)로 입력이 변한다
ADD는 COPY보다 똑똑하지만, 그 “똑똑함” 때문에 캐시 관점에서는 예측이 어려워질 때가 있습니다.
- 로컬 tar를
ADD하면 자동으로 풀립니다(결과 디렉터리 내용이 입력에 민감). - 원격 URL을
ADD https://...로 가져오면, 원격 리소스 변경에 따라 캐시가 깨지거나(혹은 기대와 다르게 캐시가 유지되거나) 동작이 흔들릴 수 있습니다.
권장
- 단순 파일 복사는
COPY사용 - 원격 다운로드는
RUN curl/wget로 처리하고, 가능하면 체크섬 검증
RUN curl -fsSL -o /tmp/tool.tgz https://example.com/tool.tgz \
&& echo "<sha256> /tmp/tool.tgz" | sha256sum -c - \
&& tar -xzf /tmp/tool.tgz -C /usr/local/bin \
&& rm -f /tmp/tool.tgz
7) BuildKit/플랫폼/빌더가 바뀌어 캐시 스코프가 달라진다
로컬에서는 빠른데 CI에서는 느린 경우, Dockerfile 자체보다 빌더 환경이 바뀌는 경우가 많습니다.
대표 케이스:
docker build(legacy) vsdocker buildx build(BuildKit)--platform linux/amd64↔linux/arm64변경- CI 러너가 매번 새 머신이라 로컬 캐시가 없음
- 원격 캐시를 안 쓰거나, 캐시 export/import 설정이 없음
해결: 빌드 캐시를 명시적으로 공유(예: registry cache)
docker buildx build \
--platform linux/amd64 \
--cache-from type=registry,ref=ghcr.io/acme/app:buildcache \
--cache-to type=registry,ref=ghcr.io/acme/app:buildcache,mode=max \
-t ghcr.io/acme/app:latest \
--push \
.
추가 팁: 의존성 다운로드 캐시 마운트(빌드 속도 체감 큼)
# syntax=docker/dockerfile:1.7
FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN \
npm ci
COPY . .
RUN npm run build
이 방식은 레이어 캐시가 깨져도 네트워크 다운로드 비용을 크게 줄여줍니다.
캐시 문제를 빠르게 진단하는 방법
원인을 정확히 잡으려면 “어느 레이어부터 캐시 미스가 나는지”를 확인해야 합니다.
1) BuildKit 진행 로그로 확인
DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp:dev .
로그에서 CACHED가 끊기는 지점을 찾으면, 그 명령의 입력(복사한 파일, ARG 값, 베이스 이미지 등)을 역추적하면 됩니다.
2) 이미지 히스토리로 레이어 확인
docker history --no-trunc myapp:dev
실무 체크리스트(요약)
COPY . .는 최대한 뒤로 미루고, 락파일/메타 파일을 먼저 복사한다..dockerignore로 컨텍스트 변동 요인을 제거한다.apt-get update는 install과 한 레이어로 묶고 정리한다.FROM ...:latest를 피하고 버전/디제스트 고정한다.ARG/ENV변경은 마지막으로 미루고, 가능하면LABEL로 대체한다.ADD대신COPY/RUN curl+ 체크섬 검증으로 결정성을 높인다.- CI에서는 buildx + 원격 캐시(
cache-to/cache-from)를 기본값으로 둔다.
마치며: 캐시 최적화는 “재현성” 최적화다
Docker 빌드 캐시는 단순 성능 기능이 아니라, 빌드가 얼마나 결정적으로 재현되는지를 드러내는 지표입니다. 위 7가지를 정리해두면 “왜 갑자기 전체 빌드가 다시 도는지”를 대부분 설명할 수 있고, CI 비용도 눈에 띄게 줄어듭니다.
컨테이너가 배포된 뒤에는 빌드보다 런타임 이슈가 더 큰 비용을 만들기도 합니다. 운영 중 디버깅이 필요하다면 Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅 같은 런타임 진단 글도 함께 참고해두면, 빌드/배포/운영 전 구간에서 문제를 더 빨리 좁힐 수 있습니다.