- Published on
Docker BuildKit 캐시 무효화 원인·해결 8가지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
Docker BuildKit은 기존 Docker 빌더보다 훨씬 공격적으로 캐시를 재사용하고, 병렬 빌드·프론트엔드 확장·RUN --mount=type=cache 같은 기능으로 빌드 시간을 크게 줄여줍니다. 그런데도 CI에서 “어제는 캐시가 잘 먹었는데 오늘은 전부 다시 빌드” 같은 상황이 반복됩니다. 이 문제는 BuildKit이 “캐시를 못 쓰는 것”이라기보다, 캐시 키를 구성하는 입력(컨텍스트/명령/환경/시크릿/네트워크/시간 등)이 조금이라도 달라져서 정상적으로 무효화되는 경우가 대부분입니다.
이 글에서는 BuildKit 캐시가 무효화되는 대표 원인 8가지를 실제로 자주 터지는 순서로 정리하고, 각 원인별로 재현 포인트, 진단 방법, 해결 패턴을 제시합니다. CI(GitHub Actions 등)에서의 원격 캐시(--cache-to/--cache-from)까지 포함합니다.
BuildKit 캐시가 “무효화”되는 방식 간단 정리
BuildKit은 각 레이어(정확히는 LLB 그래프의 노드)에 대해 캐시 키를 만들 때 대략 다음 입력들을 고려합니다.
- Dockerfile의 해당 단계 명령 문자열 및 인자
- 이전 단계 결과(부모 레이어)
COPY/ADD대상 파일의 내용(해시)과 메타데이터 일부- 빌드 인자(
ARG)·환경 변수(ENV)·플랫폼(--platform) 등 RUN에서 사용되는 마운트(시크릿/SSH/캐시/바인드)의 입력- 빌드 컨텍스트 범위(전송된 파일 집합)
즉, **캐시가 깨지는 이유는 대부분 “내가 바꾼 줄 몰랐던 입력이 바뀌었기 때문”**입니다.
1) .dockerignore 부재/오설정으로 컨텍스트가 매번 바뀜
증상
- 코드 변경이 거의 없는데
COPY . .이후 단계가 매번 무효화 - CI에서
.git/,node_modules/,dist/, 로그 파일 등이 포함되어 캐시가 흔들림
원인
Build context에 포함되는 파일 중 하나라도 변경되면 COPY 입력 해시가 바뀌고, 그 이후 레이어가 연쇄적으로 무효화됩니다. 특히 .git 디렉터리나 빌드 산출물, 테스트 리포트 같은 파일은 CI마다 내용이 달라지기 쉽습니다.
해결
.dockerignore를 먼저 정리하고,COPY범위를 최소화합니다.
.git
node_modules
**/*.log
coverage
.dist
build
.DS_Store
# 나쁜 예: 컨텍스트 전체 복사
# COPY . .
# 좋은 예: 필요한 것만 단계적으로 복사
COPY package.json package-lock.json ./
RUN npm ci
COPY src ./src
2) COPY . .의 “순서”가 의존성 캐시를 매번 깨뜨림
증상
npm ci,pip install,go mod download가 매번 다시 실행됨
원인
의존성 설치보다 먼저 전체 소스를 복사하면, 소스 변경이 의존성 레이어의 캐시 키까지 바꿔버립니다.
해결
- “의존성 정의 파일만 먼저 복사 → 설치 → 나머지 소스 복사” 패턴을 지킵니다.
# Node 예시
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --no-audit
COPY . ./
RUN npm run build
Python도 동일합니다.
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN pip install -U pip && pip install poetry && poetry install --no-root
COPY . ./
CMD ["python", "main.py"]
3) ARG/ENV가 캐시 키를 흔들어 의도치 않게 전 단계 무효화
증상
--build-arg BUILD_NUMBER=...같은 값을 매번 바꾸면, 그 이후RUN이 전부 재실행ARG가 Dockerfile 상단에 있고, 실제로는 일부 단계에서만 필요한데 전 범위에 영향
원인
ARG는 사용되는 시점부터 캐시에 영향을 줍니다. 하지만 Dockerfile 구조에 따라 불필요하게 앞 단계에서 ARG를 선언하거나 ENV로 흘려보내면 캐시가 광범위하게 깨집니다.
해결
- 변동 값은 가능한 한 마지막 단계로 미루고, 메타데이터는
LABEL에만 반영합니다.
FROM alpine:3.20
WORKDIR /app
# 빌드에 영향 주지 않도록 마지막에만 사용
ARG BUILD_SHA
LABEL org.opencontainers.image.revision=$BUILD_SHA
COPY bin/app /app/app
ENTRYPOINT ["/app/app"]
- 빌드 중에만 필요한 값이면
ARG를 해당 스테이지/단계 직전에 선언합니다.
4) RUN apt-get update 같은 네트워크 의존 명령이 캐시를 불안정하게 만듦
증상
- 같은 커밋인데도 CI에서
apt-get update단계가 자주 재실행되거나, 결과가 달라짐 - 레포지토리 미러 상태에 따라 설치 결과가 달라져 이후 레이어가 달라짐
원인
BuildKit은 네트워크 호출 자체를 “캐시 가능”하게 만들 수는 있지만, 패키지 인덱스/미러 상태가 바뀌면 결과가 달라질 수 있고, 보안 업데이트로 설치되는 버전이 달라지면 레이어 해시가 바뀝니다.
해결
apt-get update와apt-get install을 한 레이어로 묶고, 필요하면 패키지 버전을 고정합니다.- BuildKit 캐시 마운트를 사용해 패키지 캐시를 재사용합니다.
# syntax=docker/dockerfile:1.7
FROM ubuntu:24.04
RUN \
apt-get update && \
apt-get install -y --no-install-recommends curl ca-certificates && \
rm -rf /var/lib/apt/lists/*
여기서 핵심은 “레이어 캐시”만 믿지 말고, BuildKit의 type=cache로 다운로드 캐시를 살려 네트워크 비용을 줄이는 것입니다.
5) ADD의 자동 압축 해제/원격 URL 사용으로 입력이 바뀜
증상
ADD https://...로 파일을 받는데 어느 날부터 캐시가 깨지거나 결과가 달라짐ADD app.tar.gz /app가 의도치 않게 풀리면서 파일 메타데이터가 달라짐
원인
ADD는 편리하지만 “자동 압축 해제”와 “원격 URL”이라는 변수가 있어 캐시 안정성이 떨어집니다. 원격 리소스가 같은 URL이어도 내용이 바뀌면 캐시가 무효화됩니다.
해결
- 원격 다운로드는
curl/wget으로 하고 체크섬을 검증해 입력을 고정합니다.
FROM alpine:3.20
RUN apk add --no-cache curl
ARG TOOL_VER=1.2.3
ARG TOOL_SHA256=...expected...
RUN curl -fsSL -o /usr/local/bin/tool \
https://example.com/tool-${TOOL_VER}-linux-amd64 && \
echo "${TOOL_SHA256} /usr/local/bin/tool" | sha256sum -c - && \
chmod +x /usr/local/bin/tool
6) RUN에서 생성되는 타임스탬프/난수/정렬 비결정성으로 레이어가 달라짐
증상
RUN date > build.txt,npm build결과물에 빌드 시간이 포함되어 매번 변경zip/tar로 아카이브 만들 때 파일 순서/mtime 때문에 해시가 달라짐
원인
BuildKit 캐시는 “명령이 같으면 결과가 같다”는 전제에 기대는데, 빌드 산출물이 비결정적이면 캐시가 있어도 재사용 가치가 떨어지고(혹은 캐시가 깨지는 입력을 만들고), 멀티스테이지에서 최종 이미지가 매번 달라집니다.
해결
- 재현 가능한 빌드(reproducible build)를 목표로 타임스탬프를 고정합니다.
tar/zip생성 시 정렬과 mtime 옵션을 고정합니다.
# 예: tar 아카이브 재현성 확보
RUN tar --sort=name \
--mtime='UTC 2020-01-01' \
--owner=0 --group=0 --numeric-owner \
-czf /tmp/app.tar.gz /app
7) CI에서 로컬 캐시가 없거나 원격 캐시 설정이 빠짐
증상
- 로컬에서는 빠른데 CI는 항상 풀빌드
- GitHub Actions에서 매번
npm ci/go build가 처음부터
원인
CI 러너는 대개 매 실행마다 새 VM/컨테이너로 시작합니다. 즉, BuildKit 로컬 캐시가 남아있지 않습니다. BuildKit을 켰다는 사실만으로는 캐시가 “공유”되지 않습니다.
해결: buildx + registry 캐시 사용
--cache-to type=registry로 캐시를 푸시하고--cache-from으로 당겨옵니다.
docker buildx build \
--platform linux/amd64 \
-t ghcr.io/acme/myapp:sha-${GITHUB_SHA} \
--cache-to type=registry,ref=ghcr.io/acme/myapp:buildcache,mode=max \
--cache-from type=registry,ref=ghcr.io/acme/myapp:buildcache \
--push .
- 캐시 이미지(ref)는 태그를 고정(예:
buildcache)하고, 실제 앱 이미지는 커밋 SHA로 태깅하는 식으로 분리하는 게 운영상 안전합니다.
권한 이슈로 캐시 푸시/풀에 실패하면 결국 풀빌드가 되므로, OIDC 기반 권한 설정을 함께 점검하는 것도 좋습니다. 관련해서는 GitHub Actions OIDC로 AWS 권한 오류 해결하기도 참고할 만합니다.
8) BuildKit 저장소/디스크 문제로 캐시가 “사라지거나” GC로 정리됨
증상
- 어느 순간부터 캐시가 전부 날아간 것처럼 동작
- 빌드 중
no space left on device또는 갑작스러운 캐시 미스 증가
원인
BuildKit은 로컬에 캐시를 저장합니다. 디스크가 부족하면 캐시가 제대로 저장되지 않거나, Docker/BuildKit의 GC가 공격적으로 캐시를 정리할 수 있습니다. 특히 디스크가 남아도 inode가 고갈되면 파일 생성이 실패하며 캐시가 깨진 것처럼 보일 수 있습니다.
해결
- 빌드 호스트의 디스크/inode를 함께 점검합니다.
- 주기적으로 사용하지 않는 이미지/빌드 캐시를 정리하되, CI에서는 원격 캐시를 우선합니다.
# BuildKit 빌드 캐시 확인
docker buildx du
# 사용하지 않는 빌드 캐시 정리(주의)
docker buildx prune -f --filter until=240h
# Docker 전체 정리(주의)
docker system prune -af --volumes
inode 고갈은 디스크 사용량만 보고는 놓치기 쉽습니다. 관련 진단/해결은 용량 남는데 No space left? inode 고갈 해결법을 같이 보면 원인 파악이 빨라집니다.
캐시 무효화 디버깅 체크리스트
문제가 생겼을 때 “무엇이 바뀌었는지”를 빠르게 찾는 순서입니다.
COPY이전/이후로 무효화가 시작되는 지점을 확인.dockerignore로 컨텍스트가 안정적인지 확인ARG/ENV변동 값이 앞단에 섞여 있는지 확인- 네트워크/패키지 설치 단계에
type=cache를 적용했는지 확인 - CI에서 원격 캐시를 쓰는지, pull/push 권한이 정상인지 확인
- 빌드 머신 디스크/inode, BuildKit GC 정책 확인
BuildKit 로그를 더 자세히 보고 싶다면 빌드 출력 포맷을 바꿔 “어느 단계가 캐시를 탔는지” 확인하는 것도 좋습니다.
DOCKER_BUILDKIT=1 docker build --progress=plain -t myapp:dev .
결론
Docker BuildKit 캐시 무효화는 대부분 “BuildKit이 멍청해서”가 아니라, 캐시 키를 구성하는 입력이 흔들리기 때문입니다. 특히 (1) 컨텍스트 관리(.dockerignore), (2) Dockerfile 단계 설계(COPY 순서), (3) 변동 값(ARG/ENV) 격리, (4) CI 원격 캐시 도입, (5) 디스크/inode 같은 인프라 상태 점검만 제대로 해도 빌드 시간은 체감할 만큼 줄어듭니다.
빌드가 느려졌다면 무작정 --no-cache로 밀어버리기보다, “어떤 입력이 바뀌어 캐시가 깨졌는지”를 먼저 좁혀 보세요. 그 과정에서 원격 캐시와 재현 가능한 빌드 습관까지 갖추면, 팀 전체의 CI 비용과 배포 리드타임이 함께 내려갑니다.