Docker Build Cache Design
Docker image를 반복 build할 때, source의 작은 변경 때문에 dependency installation 등 비용 큰 이전 단계까지 매번 다시 실행되는 경우가 있다. 이 pattern의 핵심은 cache를 무조건 오래 쓰는 것이 아니라, 변경된 입력 뒤의 layer만 다시 build하게 Dockerfile과 build context를 설계하는 것이다.
- CI에서 같은 repository를 반복 build하거나, local development에서 image rebuild 시간이 feedback loop를 늦출 때 적용한다.
- Docker Platform의 image build lifecycle 안에서 동작하며, GitHub Actions Workflow job은 remote/external cache를 제공할 수 있는 실행 맥락이다.
Cache가 깨지는 기준
- Docker builder는 base image부터 각 instruction을 cached layer와 비교하고, match하지 않으면 cache를 invalidate한다. 한 layer가 invalidate되면 그 이후 instruction도 다시 build된다.[1]
ADD·COPY와RUN --mount=type=bind는 관련 file metadata로 checksum을 계산한다. 단순RUN은 container 안의 갱신 파일을 검사하지 않고 command string을 기준으로 cache를 찾는다.[2]
설계 원칙
- Dockerfile instruction을 덜 자주 바뀌는 입력부터 배치한다. base image와 dependency manifest를 먼저 처리하고, 자주 바뀌는 application source는 뒤에
COPY한다.[3] .dockerignore로 build에 필요 없는 file과 directory를 build context에서 제외한다.[3:1] 이렇게 하면 context 전송량과 불필요한 cache invalidation이 줄어든다는 효과는 원문에 명시되지 않은 이 page의 해석이다.COPY/ADD와 bind mount의 입력 파일 metadata 변화가 cache key에 영향을 준다는 점을 고려해,[2:1] dependency manifest와 source tree를 같은 layer에 무분별하게 넣지 않는다. 공식 예시는COPY를 package management file과 project source code 두 단계로 나눈다.[3:2]- package manager처럼 반복 다운로드가 큰 작업에는 cache mount를, CI처럼 runner가 매번 바뀌는 환경에는 external cache를 검토한다.[3:3]
이 pattern의 설계 기준은 stable dependency boundary와 volatile application boundary를 Dockerfile에서 분리하는 것이라고 볼 수 있다. 예를 들어 dependency manifest가 바뀌지 않았다면 source code만 바꿔도 dependency install layer를 재사용할 수 있다.
절충점
- Layer 분리는 build speed를 높이지만 Dockerfile structure를 더 의식적으로 설계해야 한다.
- 작은 context는 cache hit 가능성을 높이지만, build에 실제 필요한 file을 제외하면 build 자체가 실패한다.
- Cache mount/external cache는 runner 간 성능을 높일 수 있지만 cache storage·retention·access policy라는 운영 비용을 추가한다.
CI cache는 correctness를 보장하는 source of truth가 아니라 build cost를 줄이는 optimization이라는 관점에서 다뤄야 한다고 보인다. cache miss가 늘었다면 layer order와 context를, stale dependency가 의심되면 base image refresh와 --no-cache 같은 explicit invalidation을 별도 점검한다. 특정 language package manager의 Dockerfile recipe는 이 page의 범위 밖이며 별도 source가 필요하다.
흔히 놓치는 지점
COPY . .를 dependency install보다 먼저 두면 → source의 모든 변경이 dependency layer까지 invalidate한다.- secret value가 바뀌어도 build secret content 자체는 cache invalidation에 포함되지 않는다는 점을 놓치면 → 의도와 다른 cache 재사용이 생길 수 있다.
RUN apt-get update가 시간이 지나면 자동으로 새 package를 받는다고 가정하면 → stale layer를 그대로 배포하게 된다..dockerignore를 과도하게 적용하면 → build context의 필요한 manifest 또는 source가 함께 제외된다.
관련
- Docker Platform — image·container lifecycle과 build context의 상위 모델을 제공한다.
- GitHub Actions Workflow — CI job은 external cache와 cache invalidation policy를 적용하는 실행 환경이 될 수 있다.
출처
테스트 질문
- Dockerfile에서 dependency manifest와 application source를 다른
COPYlayer로 두는 이유는 무엇인가? RUNcache와COPYcache의 invalidation 기준은 어떻게 다른가?
docker-build-cache-invalidation.md — "The builder begins by checking if the base image is already cached. Each subsequent instruction is compared against the cached layers. If no cached layer matches the instruction exactly, the cache is invalidated." ↩︎
docker-build-cache-invalidation.md — "For the ADD and COPY instructions, and for RUN instructions with bind mounts... the builder calculates a cache checksum from file metadata... Aside from the ADD and COPY commands, cache checking doesn't look at the files in the container... just the command string itself is used to find a match." ↩︎ ↩︎
docker-build-cache-optimization.md — "Order your layers"(L5-34): "try to make expensive steps appear near the beginning of the Dockerfile. Steps that change often should appear near the end of the Dockerfile", "First, copy over the package management files ... Then, install the dependencies. Finally, copy over the project source code, which is subject to frequent change."; "Keep the context small"(L36-47): "create a
.dockerignorefile in the root of your build context ... lets you exclude files and directories from the build context."; "Use cache mounts"(L103-160): "even if you need to rebuild a layer, you only download new or changed packages"; "Use an external cache"(L162-210): "External caches are especially useful for CI/CD pipelines, where the builders are often ephemeral". ↩︎ ↩︎ ↩︎ ↩︎