빌드노트
Docker cache mount에 만든 파일은 실행 이미지에 남지 않는다
Docker 빌드 중에는 읽힌 파일이 실행 이미지에 없다면 RUN cache mount의 target과 산출물 경로를 대조한다. 필요한 파일을 같은 RUN에서 마운트 밖으로 복사하고, 완성한 이미지에서 두 경로를 확인하는 방법을 설명한다.
Docker 빌드의 RUN에서는 파일을 읽었는데, 완성한 이미지로 실행하면 같은 파일이 없다. 그 RUN에 --mount=type=cache가 있다면 target과 실제 산출물 경로부터 대조한다. 마운트 안에 새로 쓴 파일은 이미지 레이어에 포함되지 않는다. 실행에 필요한 파일은 같은 RUN에서 마운트 밖으로 복사하거나, 처음부터 밖에 생성해야 한다.
예를 들어 target=/build-cache이고 빌드 도구의 출력이 /build-cache/app.bin이라면, 빌드 성공만으로 실행 파일이 이미지에 들어갔다고 판단할 수 없다. 빌드가 끝난 뒤 /app/app.bin 같은 일반 경로로 옮긴다. 복사 목적지도 그 RUN의 다른 마운트 안에 들어가 있지 않은지 확인한다.
복사는 캐시를 연결한 RUN 안에서 한다
아래는 텍스트 파일 하나로 생성 위치와 복사 위치를 드러낸 RUN이다. 기존 Linux Dockerfile의 해당 명령을 고칠 때도 이처럼 빌드 명령 뒤에 복사를 잇는다. /bin/sh, mkdir, printf, cp가 있고 두 경로에 쓸 권한이 있는 베이스를 전제로 한다.
RUN --mount=type=cache,target=/build-cache \
mkdir -p /build-cache /app && \
printf 'cache-marker\n' > /build-cache/marker.txt && \
cp /build-cache/marker.txt /app/marker.txt
이미지에 남길 파일을 만드는 줄은 마지막 cp다. target 아래에 파일을 생성한 시점에는 캐시에 쓴 상태이며, /app으로 복사해야 일반 이미지 경로에도 파일이 생긴다. 별도 RUN으로 복사를 미루면 앞의 마운트가 계속 연결되어 있지 않다. 다시 마운트해야 접근할 수 있으므로, 생성한 자리에서 필요한 파일을 꺼내는 편이 분명하다.
이 문법에는 RUN --mount를 지원하는 BuildKit이 필요하다. Dockerfile 공식 문서 (새 탭에서 열림)에서 최소 Dockerfile 문법 버전은 1.2다. 아래 관찰은 Linux 이미지에 관한 것으로 Windows 컨테이너에는 적용해 검증하지 않았다.
빌드 중 읽힌 파일과 이미지의 파일은 따로 확인한다
2026년 10월 23일, 기존 로컬 alpine:3.22 베이스에 위 RUN을 적용해 비교했다. 경로와 이미지 이름은 설명용 이름으로 통일했다. 실제 베이스의 Alpine 버전은 3.22.7이었다. Windows PowerShell 5.1, Linux/amd64 Docker Engine 29.8.0, BuildKit 0.33.0, Buildx 0.37.1에서 별도 syntax 지시어 없이 내장 프런트엔드를 사용했다.
빌드는 종료 코드 0이었고, 빌드 중 두 경로를 읽는 추가 확인에서도 모두 cache-marker가 나왔다. 그다음 완성한 이미지에서 같은 두 파일을 읽었다. 아래 cache-demo:local은 수정한 Dockerfile로 빌드한 자기 이미지 태그로 바꾸고, 경로도 실제 산출물과 복사본에 맞춘다. 이 조회에는 이미지 안의 cat이 필요하며, 파일을 가릴 별도 볼륨이나 bind mount를 연결하지 않는다.
docker run --rm --network none --read-only --entrypoint cat cache-demo:local /build-cache/marker.txt /app/marker.txt
직접 실행에서는 표준 오류에 첫 줄, 표준 출력에 둘째 줄이 나왔다. 두 스트림이 화면에 표시되는 순서는 달라질 수 있다.
cat: can't open '/build-cache/marker.txt': No such file or directory
cache-marker
첫 파일이 없어서 이 조회 명령의 종료 코드는 1이다. 뒤의 cache-marker는 /app/marker.txt를 읽은 결과다. 실행 이미지에서는 캐시 경로의 파일이 없고, 같은 RUN에서 밖으로 복사한 파일은 남았다.
경로 자체의 문제인지 확인하려고 마운트 옵션만 제거한 대조군도 빌드했다. 이때는 두 경로 모두 cache-marker를 읽었고 조회가 종료 코드 0으로 끝났다. 두 이미지에는 VOLUME 선언이 없었으며 검사 컨테이너에도 마운트가 없었다. 파일을 쓸 때 그 경로가 cache mount였는지가 비교 조건의 차이였다.
캐시가 남아 있어도 이미지에는 없을 수 있다
이미지에서 파일이 없다는 결과를 캐시 파일이 삭제됐다는 뜻으로 읽으면 안 된다. 같은 캐시를 다시 연결한 후속 빌드에서는 원래 marker.txt를 읽었다. 이 재사용 확인에는 --no-cache를 넣지 않았다. 빌더의 캐시와 실행 이미지가 서로 다른 저장 경계라는 관찰이다.
cache mount 공식 설명 (새 탭에서 열림)은 캐시를 빌드 속도를 위한 저장소로 다룬다. 다른 빌드가 내용을 바꾸거나 공간 정리(GC)로 사라질 수 있으므로, 캐시가 비어 있어도 빌드가 필요한 결과를 만들 수 있어야 한다. 이번 실험은 같은 빌더에서 곧바로 재사용한 경우만 확인했으며 GC 뒤의 재빌드나 장기 보존은 검증하지 않았다.
빌드 도구가 출력 경로를 지정할 수 있다면 복사 대신 처음부터 마운트 밖에 결과를 만들 수도 있다. Docker의 캐시 최적화 예제 (새 탭에서 열림)는 Go 캐시 경로 두 곳을 마운트하고 최종 파일은 /app/hello에 출력한다. 이 글에서 직접 실행한 것은 텍스트 파일의 생성·복사·조회이며 실제 컴파일이나 애플리케이션 실행은 확인하지 않았다.
자기 Dockerfile에서는 해당 RUN의 모든 target, 빌드 도구의 출력 경로, 복사 목적지를 함께 본다. 산출물이 이미 마운트 밖에 있는데도 없다면 뒤의 명령이 지우는지, 다음 빌드 단계로 옮겼는지, 실행 시 마운트가 가리는지 확인한다. 이 비교가 검증한 범위는 단일 Linux 빌드 단계의 일반 파일이다.