빌드노트
이미지를 v2로 바꿨는데 볼륨의 기본 파일은 v1이다
빈 볼륨의 초기 복사와 내용이 있는 볼륨의 재사용을 v1·v2 파일로 비교한다. 새 이미지의 파일 추가가 기존 볼륨에 반영되지 않는 조건과 volume-nocopy의 역할을 확인하고, 이미지 배포와 데이터 이관을 분리할 기준을 설명한다.
Docker 이미지를 다시 빌드하고 컨테이너도 새로 만들었는데, named volume을 연결한 경로의 기본 파일은 예전 내용이다. 이때는 이미지 교체와 볼륨 안 파일의 갱신을 나눠 봐야 한다. Docker가 빈 볼륨에 이미지의 기본 파일을 복사하는 동작은, 배포할 때마다 볼륨을 최신 이미지와 동기화하는 기능이 아니다.
공식 문서의 조건은 두 가지다. 파일이 있는 컨테이너 경로에 빈 볼륨을 연결하면 기본적으로 그 파일을 볼륨에 복사한다. 이미 내용이 있는 볼륨을 연결하면 그 내용이 이미지 쪽 파일을 가린다. 따라서 새 이미지에 파일이 제대로 들어 있어도 같은 경로에서 옛 볼륨의 파일이 보일 수 있다. Docker의 볼륨 마운트 설명 (새 탭에서 열림)
실행 이미지와 파일이 있는 저장소부터 확인한다
다음 명령은 PowerShell 7에서 실행한다. $container에는 현재 실행 중인 컨테이너 이름이나 ID를, $image에는 배포하려던 새 이미지의 로컬 태그를 넣는다. my-app과 my-app:2는 바꿔 넣을 예시다. 파일 경로 /data/default.txt도 실제로 문제가 생긴 경로로 바꾼다. 이 파일 읽기에는 컨테이너 안의 cat이 필요하다.
아래 블록으로 컨테이너가 새 이미지를 사용하는지, 파일 경로에 어떤 볼륨이 연결돼 있는지, 지금 파일에 무엇이 들어 있는지를 확인한다. 각 명령이 실패하면 그 오류부터 해결하고 다음 출력과 비교한다.
$container = 'my-app'
$image = 'my-app:2'
docker container inspect --format 'image={{.Image}}' $container
docker image inspect --format 'image={{.Id}}' $image
docker container inspect --format '{{json .Mounts}}' $container
docker exec $container cat /data/default.txt
처음 두 줄의 image=sha256:... 값이 다르면 우선 기대한 이미지로 컨테이너가 교체됐는지 확인해야 한다. 이름이나 태그가 같다는 사실만으로는 부족하다. 이 글의 비교에서는 같은 로컬 엔진에 있는 단일 플랫폼 이미지의 ID를 대조했다. 다중 플랫폼 이미지의 인덱스와 개별 플랫폼 이미지처럼 식별 대상이 다를 때는 문자열 차이만으로 교체 실패를 단정하지 않는다. 컨테이너 조회 (새 탭에서 열림)와 이미지 조회 (새 탭에서 열림)는 각각 다른 대상을 읽는 명령이다.
이미지가 맞다면 Mounts에서 문제 파일의 경로를 덮는 항목을 찾는다. /data/default.txt의 경우 Type이 volume, Destination이 /data인 항목의 Name이 연결된 볼륨명이다. 상위 경로의 마운트도 확인하고, 마운트가 겹치면 파일에 가장 구체적으로 적용되는 항목을 본다. Type이 bind라면 이 글의 named volume 초기 복사와 구분해야 한다.
docker exec는 실행 중인 컨테이너에서 명령을 수행한다. 컨테이너가 정지했거나 cat이 없거나 읽기 권한이 없어서 실패한 것을 파일 내용이 없다는 결과로 해석하지 않는다. exec 사용 조건 (새 탭에서 열림)
v2에서 새로 추가한 파일도 기존 볼륨에는 없었다
이 차이를 확인하려고 업무 데이터 대신 합성 파일로 따로 실험했다. alpine:3.22를 바탕으로 고유한 v1·v2 이미지를 만들었다. v1에는 /data/default.txt에 v1을, v2에는 같은 파일에 v2를 넣었다. v2에는 /data/v2-only.txt도 추가하고 그 내용은 added-in-v2로 정했다. 두 이미지에는 VOLUME 선언과 앱 초기화 스크립트가 없었다.
먼저 마운트 없이 두 이미지의 파일이 각각 v1·v2인지 확인했다. 이어서 새 빈 볼륨 A를 v1의 /data에 연결하자 default=v1이 보였다. 이 컨테이너를 제거하고 v2 이미지로 새 컨테이너를 만들되 볼륨 A를 그대로 연결했다. 아래는 그 실행의 출력이다. image는 볼륨 밖에 둔 /image-version.txt를 읽은 값이다.
image=v2
default=v1
v2-only=MISSING
MISSING은 실험용 셸이 해당 파일이 없을 때 출력한 표식이다. 두 컨테이너의 ID와 사용한 이미지 ID가 모두 달라졌고, /data에 연결한 볼륨 이름은 같았다. 기본 파일이 v1로 남았을 뿐 아니라 v2에서 추가한 파일도 없었다. 기존 볼륨을 재사용하는 과정에서 이미지의 새 파일을 합쳐 넣지도 않았다.
같은 v2 이미지에 다른 새 빈 볼륨 B를 연결했을 때는 다음처럼 나왔다.
image=v2
default=v2
v2-only=added-in-v2
새 볼륨 대조는 v2 이미지에 파일이 빠진 경우와, 파일은 있지만 기존 볼륨이 가린 경우를 구별하게 해 준다. 이 실험에서는 후자였다. 운영 볼륨의 파일을 지워서 결과를 만든 것이 아니라 처음부터 따로 만든 볼륨들을 비교했다.
자기 이미지의 기본 파일도 대조하려면, 먼저 조사 경로와 그 상위 경로에 Dockerfile의 VOLUME 선언이 없는지 확인한다. 다음 명령은 그 조건을 만족하고 cat이 있는 Linux 이미지에서, 기존 named volume을 붙이지 않은 일회용 컨테이너로 파일을 읽는다. $image는 앞에서 지정한 새 이미지이고, $probeName은 현재 쓰지 않는 컨테이너 이름으로 바꾼다.
$probeName = 'seed-file-check'
docker run --rm --pull never --name $probeName --network none --read-only --entrypoint cat $image /data/default.txt
실험 이미지에서는 이 명령이 v2를 출력했고, 기존 볼륨을 연결한 컨테이너의 docker exec는 v1을 출력했다. --entrypoint cat으로 앱의 시작 절차를 거치지 않고 파일만 읽은 결과다. --pull never이므로 해당 이미지가 로컬에 없으면 실패한다. 여기서도 옛 내용이면 이미지에 들어간 파일부터 확인하고, 파일을 못 읽었다면 그 오류를 먼저 구분한다. docker run 옵션 (새 탭에서 열림)
volume-nocopy는 새 내용으로 갱신하는 옵션이 아니다
빈 볼륨 C에 v2를 연결하면서 마운트에 volume-nocopy를 지정한 경우도 비교했다. 사용한 옵션은 --mount type=volume,src=볼륨이름,dst=/data,volume-nocopy 형태다. 이 옵션은 빈 볼륨으로의 기본 복사를 막는다. 기존 파일을 새 이미지 파일로 교체하는 옵션이 아니다. 볼륨의 mount 옵션 (새 탭에서 열림)
이 경우 출력은 image=v2, default=MISSING, v2-only=MISSING이었다. 이미지에는 파일이 있지만 빈 볼륨이 /data를 덮고, 복사도 생략한 상태다. 앱이 시작할 때 파일을 따로 만드는 경우까지 막아 주는 옵션은 아니다. 이번 실험에서는 파일을 읽는 셸만 실행했다.
여기서 볼륨 C를 지우거나 다시 만들지 않고, 다음 v2 컨테이너에서 volume-nocopy만 뺐다. 아직 비어 있던 같은 볼륨에 default=v2와 v2-only=added-in-v2가 나타났다. 볼륨이 처음 생성된 순간만 기억하는 규칙으로는 이 결과를 설명할 수 없다. 이미 존재하는 볼륨이어도 연결할 때 비어 있고 기본 복사를 허용하면 파일이 복사될 수 있다. volume-nocopy를 볼륨에 영구 저장되는 복사 금지 속성으로 생각해서도 안 된다.
기본 파일 배포와 데이터 이관을 분리한다
새 컨테이너가 올바른 v2 이미지를 사용하고, 그 이미지에는 새 파일이 있으며, 문제 경로에는 내용이 있는 기존 볼륨이 연결돼 있다면 이미지 재빌드만 반복할 이유가 없다. 이제 판단할 것은 그 파일이 배포물인지, 사용자가 수정하거나 앱이 쌓은 데이터인지다.
배포마다 이미지와 함께 바뀌어야 하는 기본 파일이라면 데이터 볼륨과 경로를 분리하는 구성을 검토한다. 예를 들어 이미지의 기본값은 /usr/share/app-defaults에 두고 변경 가능한 데이터는 /data에 둔다. 경로만 옮겨서는 앱이 새 파일을 읽지 않으므로 앱의 조회 경로와 기존 설정 우선순위도 함께 정해야 한다. 이는 위 복사 실험에서 자동으로 검증된 수정안은 아니다.
기존 볼륨의 사용자 데이터를 유지하면서 일부 기본 파일을 바꿔야 한다면 별도 이관 단계가 필요하다. 덮어쓸 파일과 보존할 파일, 이전 형식에서 새 형식으로 바꿀 규칙, 백업·검증·실패 시 복귀 방법을 먼저 정한다. 새 빈 볼륨 비교는 원인을 가리는 데 유용하지만 기존 데이터의 이관을 대신하지 않는다. 볼륨 삭제나 prune을 갱신 해결책으로 삼지 않는다.
2026년 10월 22일 Windows 11, PowerShell 7.6.6, Docker의 Linux 엔진 29.8.0에서 비교했다. 고유한 이름의 실습 이미지·컨테이너·local 드라이버 볼륨만 사용했고, 실습 자원만 정리했다. 본문의 진단 명령도 별도 합성 컨테이너에서 실행해 이미지 ID 일치와 파일 내용의 v1·v2 차이를 확인했다. 관찰 범위는 일반 텍스트 파일의 초기 복사와 재사용이다. DB 형식 변경, 실제 앱의 초기화·이관, 권한·소유자 보존, 원격 볼륨 드라이버와 Windows 컨테이너는 검증하지 않았다.