본문으로 바로가기
반응형

이 글에서 사용하는 주요 용어 정의

용어 정의
빌드 컨텍스트 빌드 명령에 지정한 경로 등으로 Docker 빌더에 제공하는 파일 집합입니다.
레이어 캐시 변경되지 않은 빌드 단계의 결과를 재사용하는 기능입니다.
BuildKit Docker 이미지 빌드에 사용되는 빌더입니다.
Cache mount 빌드 중 특정 디렉터리의 캐시 데이터를 재사용하기 위한 마운트입니다.
.dockerignore 빌드 컨텍스트에서 제외할 파일 패턴을 지정하는 파일입니다.

애플리케이션 코드만 수정했는데 패키지를 다시 설치한다면 의존성 파일을 먼저 복사하고 설치한 뒤, 소스를 복사하는 순서부터 확인하세요. 설치 단계가 재실행되는 경우에는 cache mount로 패키지 다운로드 캐시를 재사용하도록 구성할 수 있습니다. Docker: Optimize cache usage in builds

기준일은 2026년 9월 8일입니다. 아래 Dockerfile과 보조 파일은 독자가 직접 빌드를 비교할 수 있도록 작성한 예제입니다. 작성 환경에 Docker가 없어 이미지 빌드·컨테이너 실행·소요 시간 측정은 하지 않았습니다.

COPY 뒤의 설치 단계가 소스 변경에 영향을 받습니다

의존성을 설치하기 전에 모든 파일을 복사하면 애플리케이션 변경도 설치 단계의 캐시 재사용에 영향을 줄 수 있습니다. 먼저 다음 Dockerfile의 순서를 보겠습니다.

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN python -m pip install -r requirements.txt
CMD ["python", "app.py"]

이 예제에서는 app.py와 requirements.txt가 함께 복사됩니다. app.py 내용이 바뀌어 COPY . . 단계의 캐시가 무효화되면, 그 뒤에 있는 설치 단계도 재실행 대상이 됩니다. 코드와 패키지 목록의 변경 주기를 Dockerfile에서 구분하지 않은 구성입니다. Docker: Build cache invalidation

설치가 다시 실행된다는 것과 패키지를 전부 새로 다운로드한다는 것은 별개입니다. 다른 캐시가 남아 있을 수 있으므로 빌드 로그에서 어떤 단계가 실행됐는지와 어떤 데이터를 다시 받았는지를 나누어 관찰해야 합니다.

의존성 설치와 소스 복사를 나누는 완결된 예제

아래 네 파일을 같은 디렉터리에 저장합니다. 예제 앱은 Requests로 요청 객체만 만들고 내용을 출력하며 외부 서버에 요청을 전송하지 않습니다. 작은 앱으로 구성해 소스 변경과 패키지 설치 단계의 관계를 보기 쉽게 했습니다.

cache-demo/
  Dockerfile
  requirements.txt
  .dockerignore
  app.py

Dockerfile: requirements.txt를 먼저 복사합니다

설치 단계보다 앞에서 복사하는 파일을 requirements.txt로 제한합니다. app.py는 설치가 끝난 뒤 복사하므로 해당 파일만 바꿀 때 앞선 설치 결과를 재사용할 수 있는 구조입니다.

# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app

COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip \
    python -m pip install -r requirements.txt

COPY app.py .
CMD ["python", "app.py"]

RUN --mount=type=cache는 이 빌드 단계에 pip 다운로드 캐시 디렉터리를 연결합니다. 위 예제는 기본 사용자로 설치를 실행하므로 /root/.cache/pip를 사용했습니다. 사용자나 pip 캐시 설정을 바꾸면 그에 맞춰 경로도 확인해야 합니다. Dockerfile reference: RUN --mount=type=cache

여기서 마운트하는 것은 다운로드 캐시 경로입니다. 애플리케이션에서 사용할 Requests 패키지는 pip install로 설치합니다. cache mount를 실행 중인 컨테이너가 의존성을 읽는 위치처럼 해석하면 안 됩니다.

requirements.txt: 예제에 사용할 버전을 지정합니다

예제는 다음 패키지 버전을 사용합니다. 최신 버전이나 운영 권장 버전이라는 뜻은 아닙니다.

requests==2.34.2

공식 Python 이미지 목록에서 3.12-slim 태그를 확인했고, PyPI에서 Requests 2.34.2와 Python 3.10 이상 요구사항을 확인했습니다. 다만 이는 두 구성요소의 공개 정보를 확인한 것이며, 이 조합의 Docker 빌드 성공을 검증한 결과는 아닙니다. Docker Hub: Python, PyPI: Requests 2.34.2

.dockerignore: 빌드에 불필요한 파일을 제외합니다

이 앱에는 로컬 가상환경, Python 바이트코드, Git 기록이 필요하지 않으므로 다음과 같이 제외합니다.

.git
.venv
__pycache__
*.pyc

.dockerignore는 불필요한 파일을 빌드 컨텍스트에서 제외하는 구성입니다. 반면 COPY requirements.txt .는 특정 단계에 복사할 파일을 정합니다. 두 설정은 서로 다른 위치에서 파일 범위를 좁힙니다. Docker: Optimize cache usage in builds

프로젝트에 같은 패턴을 그대로 넣기 전에 빌드가 실제로 필요한 파일을 확인하세요. 예를 들어 버전 생성 과정에서 Git 기록을 읽는 프로젝트라면 이 작은 예제와 조건이 다릅니다. 여기에 제시한 목록은 네 파일로 구성된 데모의 범위에 맞춘 것입니다.

app.py: 네트워크 호출 없이 패키지를 사용합니다

다음 코드는 준비한 요청의 HTTP 메서드와 URL을 출력합니다. Requests 문서에서 요청 준비와 전송은 별도 단계이며, 이 예제는 전송 함수를 호출하지 않습니다. 아직 컨테이너에서 실행하지 않았으므로 실행 결과 블록은 제공하지 않습니다. Requests: Prepared Requests

import requests

request = requests.Request(
    "GET", "https://example.com/", params={"source": "cache-demo"}
).prepare()

print("cache demo")
print(request.method, request.url)
# 네트워크로 전송하지 않고 요청 객체만 준비합니다.

독자는 이후 print("cache demo")의 문자열만 바꿔 소스 변경 실험을 진행할 수 있습니다. 패키지 목록이나 Dockerfile을 동시에 수정하지 않아야 어느 변경이 설치 단계에 영향을 주었는지 구분하기 쉽습니다.

레이어 캐시와 cache mount는 작동 시점이 다릅니다

레이어 캐시를 재사용하면 해당 설치 명령 자체를 건너뛸 수 있습니다. Cache mount는 설치 명령을 다시 실행할 때 다운로드 캐시를 활용하는 수단입니다. 설치 단계가 건너뛰어진 것과 재실행 비용이 줄어든 것을 같은 결과로 기록하지 마세요.

구분 레이어 캐시 Cache mount
재사용 대상 이전 빌드 단계의 결과 지정한 디렉터리의 캐시 데이터
설치 명령과의 관계 캐시가 유효하면 명령을 건너뜁니다. 명령을 실행하면서 데이터를 이용합니다.
이 예제의 적용 위치 requirements 복사·설치 단계 pip 다운로드 캐시
캐시가 없는 경우 단계를 실행해야 합니다. 필요한 데이터를 다시 받아야 합니다.

Docker는 cache mount를 성능 최적화 수단으로 설명하며, 캐시 내용에 의존하지 않아도 빌드가 동작해야 한다고 안내합니다. 가비지 수집 등으로 내용이 사라질 수 있으므로 항상 남는 저장소로 취급해서는 안 됩니다. Dockerfile reference: cache mount

따라서 예제에 --no-cache-dir을 함께 넣지 않았습니다. 여기서는 pip 다운로드 캐시를 활용하도록 구성했기 때문입니다. 이 옵션을 기계적으로 복사하기보다 설치 도구의 캐시 위치와 Dockerfile의 마운트 위치가 같은지 확인하는 것이 좋습니다.

소스 변경과 의존성 파일 변경을 따로 비교하세요

네 파일을 저장한 cache-demo 디렉터리에서 아래 명령을 실행할 수 있습니다. 명령 끝의 .은 현재 디렉터리를 빌드 컨텍스트로 지정합니다. BuildKit의 cache mount를 지원하는 Docker 빌드 환경이 필요합니다.

docker build --progress=plain -t cache-demo .
docker run --rm cache-demo

첫 빌드가 끝나면 같은 빌더에서 파일을 바꾸지 않고 다시 빌드합니다. 그 다음 app.py의 출력 문자열만 바꾸고 다시 실행합니다. 마지막으로 requirements.txt 끝에 # dependency-change-demo 주석 한 줄을 추가해 파일 내용 변경이 설치 단계에 주는 영향을 관찰합니다.

의존성 파일의 주석 변경은 실제 패키지 변경 없이 캐시 무효화 경계를 살펴보려는 실험입니다. 새로운 패키지 버전이 다운로드되는 상황까지 검증하는 실험은 아닙니다.

아래 표는 Docker 캐시 규칙을 이 예제에 적용한 예상 관찰 항목입니다. 실제 빌드 출력이나 측정 결과가 아니며, 같은 빌더의 캐시가 남아 있고 기반 이미지 등 다른 입력이 같다는 조건을 전제로 합니다.

변경 조건 설치 RUN에서 살필 점 해석
캐시 없는 최초 빌드 설치 명령 실행 재사용할 이전 결과가 없습니다.
입력을 그대로 두고 다시 빌드 해당 단계의 캐시 재사용 표시 설치 결과를 재사용할 수 있습니다.
app.py 문자열만 변경 설치 단계 캐시 재사용 여부 소스 복사가 설치 뒤에 분리됐는지 확인합니다.
requirements.txt 주석 추가 설치 명령 재실행 여부 파일 내용 변경으로 앞선 복사 결과가 달라집니다.

설치 명령이 재실행됐다는 사실만으로 다운로드 캐시가 사용됐다고 확정하지 마세요. 설치 로그를 따로 읽어야 하며, 패키지나 빌더 환경에 따라 캐시를 활용하는 정도가 달라질 수 있습니다. 이 글은 몇 초 또는 몇 배 빨라진다는 결과를 제시하지 않습니다.

캐시 최적화와 의존성 최신성은 별도로 관리합니다

시간이 지났다는 이유만으로 RUN 캐시가 자동 무효화되지는 않습니다. 캐시가 재사용됐다는 사실은 의존성이 최신이라는 뜻이 아니므로, 의존성을 갱신하려면 변경 대상과 빌드 입력을 명시적으로 관리해야 합니다. Docker: Build cache invalidation

또한 python:3.12-slim은 내용이 바뀔 수 있는 태그이며, 예제의 requirements는 간접 의존성까지 고정하지 않았습니다. 이 네 파일만으로 시간이 지나도 완전히 같은 이미지가 만들어진다고 보장할 수 없습니다. 다른 빌더에서도 로컬 캐시가 자동으로 공유된다고 가정하지 마세요.

먼저 현재 Dockerfile에서 의존성 설치보다 앞에 있는 첫 COPY를 확인해 보세요. 소스 전체를 먼저 복사하고 있다면 의존성 파일과 앱 파일을 나눈 뒤, 소스만 수정했을 때 설치 단계가 재사용되는지 빌드 로그로 확인하면 됩니다.

참고 자료

반응형