빌드노트
touch했는데 Docker COPY가 CACHED로 나오는 이유
touch 뒤 COPY가 CACHED여도 mtime만 바뀌었다면 정상적인 캐시 재사용이다. 동일 입력·시간만 변경·내용·권한 변경을 실제 빌드 로그와 이미지 안 해시로 비교하고, 크기와 mtime을 함께 보존한 로컬 입력의 예외 관찰도 구분한다.
파일을 touch한 뒤 Docker 이미지를 다시 빌드했는데 COPY가 CACHED라면, 먼저 파일 내용도 바뀌었는지 확인한다. 수정 시각인 mtime만 바뀐 것은 `COPY` 캐시를 무효화하는 조건이 아니다. 이 로그만으로 Docker가 바뀐 파일 내용을 무시했다고 판단할 수 없다.
Docker의 캐시 무효화 문서 (새 탭에서 열림)는 COPY 대상 파일의 메타데이터로 캐시 체크섬을 계산하되 mtime은 제외한다고 설명한다. 여기서 “메타데이터를 본다”는 말을 “날짜가 달라지면 다시 복사한다”로 읽으면 결과를 반대로 예상하게 된다.
시간과 내용부터 나눠 확인한다
PowerShell에서 다음 두 명령으로 COPY의 원본 파일을 확인할 수 있다. payload.txt는 자신의 Dockerfile이 복사하는 실제 소스 경로로 바꾼다. 변경 전후에 각각 실행해 해시와 시각을 따로 비교한다.
Get-FileHash -Algorithm SHA256 .\payload.txt
(Get-Item -LiteralPath .\payload.txt).LastWriteTimeUtc
mtime은 달라도 SHA-256이 같다면 파일의 바이트는 같다. 반대로 같은 크기의 파일이라도 해시가 다르면 내용이 달라졌다. 이 SHA-256은 독자가 내용 차이를 확인하기 위한 값이다. Docker의 캐시 체크섬 전체와 같은 값이라는 뜻은 아니다.
작은 COPY와 RUN에서 비교한 결과
운영 프로젝트 대신 12바이트짜리 version=one과 LF 줄바꿈을 가진 파일로 비교했다. Dockerfile과 베이스 이미지는 고정하고 파일 입력만 바꿨다. 아래는 같은 비교를 해 볼 수 있는 Dockerfile이다. 빈 실습 폴더에 Dockerfile이라는 이름으로 저장한다. Docker의 Linux 컨테이너 엔진과 PowerShell 7이 필요하며, 베이스 이미지가 없으면 레지스트리 접근도 필요하다.
FROM docker.io/library/busybox@sha256:66a6306db78bf2dbf3487f293aa8d6990d8e506fdffab9cc43fe422becf886e4
COPY payload.txt /probe/payload.txt
RUN sha256sum /probe/payload.txt
베이스는 이번에 사용한 BusyBox 1.37.0의 Linux amd64 이미지다. ARM 환경에서는 이 실험과 같은 amd64 실행 지원이 필요하다. 다음 명령은 LF를 포함한 원본 파일을 만들고, 같은 입력을 두 번 빌드해 재사용 여부를 확인한다. 태그는 기존 이미지와 겹치지 않는 자신의 실습용 이름으로 바꾼다. 이후 명령도 같은 폴더와 같은 태그로 실행하며, 빌드가 실패하면 다음 단계로 넘어가지 않는다.
$payload = Join-Path $PWD 'payload.txt'
$tag = 'copy-mtime-check:local'
[IO.File]::WriteAllBytes($payload, [Text.Encoding]::UTF8.GetBytes("version=one`n"))
docker build --progress=plain -t $tag .
docker build --progress=plain -t $tag .
이번 실행에서는 첫 빌드의 COPY와 RUN이 실행됐고, 동일 입력 재빌드에서는 둘 다 CACHED였다. plain 출력 옵션 (새 탭에서 열림)은 단계별 진행을 텍스트로 남기므로 이 비교에 썼다. 새 이미지 태그를 붙이는 것만으로 캐시가 비워지는 것은 아니다.
다음은 내용을 다시 쓰지 않고 mtime만 한 시간 뒤로 옮기는 명령이다. 파일 내용은 그대로 둔 채 시간 변경만으로 재실행되는지 확인한다.
(Get-Item -LiteralPath $payload).LastWriteTimeUtc = (Get-Item -LiteralPath $payload).LastWriteTimeUtc.AddHours(1)
docker build --progress=plain -t $tag .
변경 전후 원본의 SHA-256은 같았다. 이때 나온 빌드 로그에서 필요한 부분만 발췌하면 다음과 같다.
#6 [2/3] COPY payload.txt /probe/payload.txt
#6 CACHED
#7 [3/3] RUN sha256sum /probe/payload.txt
#7 CACHED
뒤의 RUN에도 캐시가 적용됐으므로, RUN 안의 해시 명령이 이번 빌드에서 새로 실행된 것은 아니다. 이미지 안의 현재 내용은 빌드 후 별도 컨테이너에서 읽어 확인했다.
이번에는 one을 같은 길이의 two로 바꾼다. 아래 명령은 내용을 수정한 뒤 다시 빌드하고, 방금 만든 이미지에서 파일 해시를 읽는다. 이 조건에서는 수정 후 mtime을 이전 값으로 되돌리지 않는다.
[IO.File]::WriteAllBytes($payload, [Text.Encoding]::UTF8.GetBytes("version=two`n"))
docker build --progress=plain -t $tag .
docker run --rm --network=none $tag sha256sum /probe/payload.txt
내용 변경 뒤에는 COPY와 RUN 모두 CACHED 없이 실행됐다. 별도 컨테이너에서 읽은 전체 SHA-256은 다음과 같다. 첫 줄은 최초·동일 입력·mtime만 변경한 이미지에 공통으로 나온 값이고, 둘째 줄은 내용 변경 뒤의 값이다.
50728d3b8cec6303162d4c47d752c78c96d123a0d598a43b9db2a99a31b711ca /probe/payload.txt
0204041343e5c623b0e0241b28dc97bf6229f6014ce054dd2102ecd51a100ebc /probe/payload.txt
빌드 시간의 길이가 아니라 해당 단계의 CACHED 표시와 이미지 내부 해시가 판단 근거다. 첫 빌드부터 캐시가 있어도 비교할 수 있지만, 이전 캐시가 삭제됐거나 Dockerfile·베이스·다른 선행 입력까지 바뀌었다면 mtime만 바꾼 조건에서도 재실행될 수 있다.
내용이 같아도 권한 변경은 별도 입력이다
파일 해시가 같다고 COPY가 반드시 캐시를 쓰는 것도 아니다. Windows 폴더의 권한 변환을 비교에 섞지 않으려고, 추가 실험에서는 파일 내용·Unix 권한·mtime을 지정한 tar를 빌드 컨텍스트로 전달했다. Docker는 tar 입력도 파일 시스템 컨텍스트로 처리한다. (새 탭에서 열림)
이 실험에서 확인한 차이는 다음과 같다.
- 내용과 권한
0644를 유지하고 mtime만 변경하면COPY와RUN모두CACHED였다. - 같은 12바이트 크기·권한·mtime을 유지하고
one을two로 바꾸면 둘 다 다시 실행됐다. two의 내용과 mtime을 유지하고 권한만0644에서0755로 바꿔도 둘 다 다시 실행됐다. 이미지 내부 SHA-256은 같았고,stat으로 읽은 권한은755였다.
따라서 내용 해시는 바이트 비교에 쓰고, 캐시 여부를 설명할 때는 권한 같은 입력도 따로 확인해야 한다. mtime이 제외된다는 문서 규칙을 모든 메타데이터가 무시된다는 뜻으로 넓힐 수 없다.
내용도 바꿨는데 계속 CACHED라면
추가 확인 중에는 다른 결과도 있었다. 로컬 폴더의 파일을 같은 크기의 다른 내용으로 덮어쓴 다음 mtime을 직전 값과 정확히 같게 복원하자, COPY가 CACHED였고 원본과 이미지 안의 해시도 달랐다. 첫 시도의 예상 검사가 실패한 뒤 별도 PowerShell 실행에서도 이 조건을 확인했다. tar로 내용을 직접 전달한 위 비교에서는 같은 크기·mtime의 내용 변경이 반영됐다.
이는 이번 환경에서 직접 관찰한 차이다. 로컬 파일 변경 전달 과정의 어느 지점에서 이전 입력이 재사용됐는지는 추적하지 못했다. 내용을 바꾸면서 시각까지 보존하는 도구를 쓴 경우에는, 로컬 해시와 방금 빌드한 이미지의 해시를 함께 확인한다. 이 조건을 단순히 touch만 한 경우와 섞거나, mtime이 COPY 체크섬에 들어간다는 근거로 삼으면 안 된다.
자신의 프로젝트에서 해시가 계속 다르다면 실제 COPY 소스, 빌드 명령 마지막의 컨텍스트 경로, ignore 규칙부터 대조한다. 이미지 안의 대상 경로는 자신의 Dockerfile에 맞춰 읽고, sha256sum이 없는 이미지라면 그 이미지에서 사용할 수 있는 해시 도구나 파일 추출 방식을 사용한다. 기존 컨테이너의 파일을 읽으면 방금 빌드한 이미지의 검증과 섞일 수 있다.
직접 실행한 환경은 Windows 11, PowerShell 7.6.6, Python 3.12.10, Docker Desktop 4.92.0, Docker Engine 29.8.0, Buildx 0.37.1, BuildKit 0.33.0의 Linux amd64 엔진이었다. 단일 파일과 직렬 COPY·RUN을 비교했다. Unix 소유자 변경, 확장 속성, 심볼릭 링크, 다른 빌더 버전은 검증하지 않았다. 이 결과는 mtime만으로 재빌드를 유도할 수 없다는 판단과 확인한 내용·권한 조건까지의 근거다.