빌드노트
볼륨은 있는데 volume-subpath를 지정하면 시작에 실패한다
named volume이 있어도 volume-subpath로 지정한 하위 디렉터리가 없으면 시작이 실패한다. 별도 초기화 뒤 같은 컨테이너의 상태와 실제 읽힌 파일을 비교하고, Mounts 출력만으로 성공을 판단할 수 없었던 이유를 설명한다.
이름 있는 볼륨을 만들었는데 volume-subpath=reports를 붙인 컨테이너가 시작하지 않는다면, 볼륨 이름과 볼륨 안의 reports 디렉터리를 나눠 확인해야 한다. docker volume create로 볼륨이 생겨도 그 안의 하위 디렉터리까지 생기지는 않는다. volume-subpath는 이미 있는 하위 경로를 골라 마운트하는 옵션이다. 필요한 경로는 애플리케이션보다 먼저 실행하는 초기화 컨테이너에서 준비한다. 이 조건은 Docker의 볼륨 하위 디렉터리 문서 (새 탭에서 열림)에 명시돼 있다.
새 볼륨으로 비교해 보니, 없는 경로 때문에 실패한 컨테이너도 Mounts에는 볼륨과 대상 경로가 표시됐다. 따라서 Mounts가 있다는 사실만으로 시작 성공이나 하위 경로의 존재를 판단할 수 없었다.
먼저 옵션 지원과 실패한 경로를 확인한다
volume-subpath의 CLI 옵션과 Engine 지원은 Docker 26.0.0에서 추가됐다 (새 탭에서 열림). API에서는 1.45의 VolumeOptions.Subpath (새 탭에서 열림)에 해당한다. 아래 명령은 PowerShell에서 실행한다. report-reader와 reports-data는 설명용 이름이므로 실패한 자신의 컨테이너와 그 컨테이너가 사용하는 볼륨 이름으로 바꾼다.
이 조회로 클라이언트·서버 버전, 요청한 하위 경로, 시작 실패 상태를 함께 확인한다.
$container = 'report-reader'
$volume = 'reports-data'
docker version
docker volume inspect $volume --format '{{.Name}}'
docker inspect $container --format '{{json .HostConfig.Mounts}}'
docker inspect $container --format 'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}'
docker version에서 Client와 Server를 모두 본다. 오래된 CLI가 옵션을 거부하거나 연결한 Engine/API가 지원하지 않는다면, 디렉터리를 만드는 것부터 시도할 문제가 아니다. 이번 실행은 Windows의 Docker CLI 29.8.0, Docker Desktop의 Linux Engine 29.8.0, API 1.56에서 했다. 최소 지원 버전인 26.0.0 자체를 실행한 것은 아니다.
HostConfig.Mounts에서는 Type이 volume인지, Source가 의도한 볼륨 이름인지, VolumeOptions.Subpath가 reports인지 확인한다. Target=/selected는 컨테이너 안의 연결 위치이고, reports는 볼륨 내부에서 찾을 경로다. 호스트의 작업 폴더 이름을 넣는 자리가 아니다. 볼륨 조회가 실패하거나 Source 이름이 다르면 초기화 명령을 실행하기 전에 대상을 바로잡는다.
실험에서는 새 볼륨만 만든 뒤 volume-subpath=reports로 시작했다. 볼륨 조회는 성공했지만 컨테이너 상태는 created, State.ExitCode는 128이었다. 오류의 볼륨 이름과 긴 경로를 줄여 옮기면 다음과 같다.
cannot access path .../_data/reports: lstat .../_data/reports: no such file or directory
이 숫자를 애플리케이션이 실행되어 반환한 종료 코드로 읽으면 안 된다. 이 실행에서는 시작 시각이 설정되지 않았고 컨테이너 로그도 비어 있었다. 볼륨을 붙이는 단계에서 reports를 찾지 못한 것이다.
하위 경로를 쓰는 컨테이너보다 초기화가 먼저다
reports가 없을 때 애플리케이션의 시작 스크립트에 mkdir를 넣어도 이번 실패를 해결하지 못한다. 그 스크립트가 실행되려면 먼저 마운트가 성공해야 하기 때문이다. 별도 초기화 컨테이너에는 하위 경로 옵션 없이 볼륨 전체를 붙인다.
다음 명령은 확인한 볼륨에 reports와 archive 디렉터리를 준비한다. 셸과 mkdir가 있는 로컬 이미지가 필요하다. 예시의 busybox:1.37.0은 미리 로컬에 있어야 하며, 다른 이미지를 쓴다면 $image를 바꾼다.
$image = 'busybox:1.37.0'
docker run --rm --pull never --network none --mount "type=volume,src=$volume,dst=/seed,volume-nocopy" $image sh -c 'mkdir -p /seed/reports /seed/archive'
if ($LASTEXITCODE -ne 0) { throw '볼륨 초기화가 실패했습니다.' }
이 컨테이너의 /seed가 볼륨의 최상위다. 따라서 /seed/reports를 만들면 다음 컨테이너가 volume-subpath=reports로 선택할 경로가 준비된다. volume-nocopy는 이미지의 대상 디렉터리 내용을 빈 볼륨으로 복사하지 않도록 넣었다. 여기서 필요한 디렉터리는 mkdir가 만든다.
docker run의 --pull never (새 탭에서 열림)는 로컬에 이미지가 없으면 실패하게 한다. 이미지 부족이나 초기화 실패를 무시하고 다음 단계로 넘어가면 안 된다. 초기화가 성공한 뒤 애플리케이션 컨테이너를 시작하는 순서를 배포 절차에도 둔다. 이 명령은 디렉터리만 준비하며 업무 데이터 파일을 채우지는 않는다.
실제 비교에서는 BusyBox 1.37.0 기반의 로컬 실험 이미지와 새 볼륨을 사용했다. 초기화 컨테이너가 두 디렉터리와 각각의 표식 파일을 만들고 종료 코드 0으로 끝났다. reports/report.txt에는 report-marker, archive/archived.txt에는 archive-marker를 넣었다. 기존 업무 볼륨에서 파일을 생성하거나 데이터를 옮긴 실험은 아니다.
같은 마운트 설정에서 실제로 보이는 파일이 달라졌다
실패한 컨테이너가 남아 있고 설정이 맞다면, 초기화 후 같은 컨테이너를 다시 시작해 결과를 확인할 수 있다.
docker start --attach $container
docker inspect $container --format '{{json .Mounts}}'
docker inspect $container --format 'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}'
실험에서는 컨테이너 ID와 요청한 마운트 설정을 그대로 유지했다. 볼륨의 reports만 /selected에 읽기 전용으로 연결한 컨테이너가 다음 내용을 출력하고 exited, 종료 코드 0, 빈 Error를 남겼다.
report.txt
report-marker
archive-visible=no
첫 두 줄은 /selected의 파일 목록과 /selected/report.txt의 내용이다. 마지막 줄은 실험용 검사 문구다. /selected/archive/archived.txt와 /selected/archived.txt가 모두 없는지 검사한 뒤 출력했다. reports 디렉터리 자체가 한 번 더 들어가는 구조도 아니었다. 볼륨의 reports 안 내용이 컨테이너의 /selected 바로 아래에 보였다.
반면 볼륨 전체를 읽기 전용으로 붙인 별도 확인 컨테이너에서는 reports/report.txt와 archive/archived.txt, 두 표식의 내용이 모두 읽혔다. 선택하지 않은 archive가 지워진 것이 아니라, 이 마운트로 노출되지 않은 것이다.
여기서 Mounts를 읽을 때 주의할 부분이 있다. 이번 Engine의 출력은 실패 전후 모두 같은 Name, Destination=/selected, RW=false를 보여 줬고, Source도 하위 경로가 붙지 않은 볼륨의 _data 경로였다. 실제 선택한 reports는 HostConfig.Mounts의 VolumeOptions.Subpath에서 확인했다. 요청 설정, 시작 상태, 읽힌 파일을 함께 대조해야 하위 경로 마운트가 성공했는지 알 수 있었다.
확인한 범위는 Docker Desktop의 Linux 컨테이너와 local 드라이버다. 초기화와 파일 읽기는 기본 root 사용자로 실행했으므로 애플리케이션 사용자로 쓰기 가능한지까지 검증하지는 않았다. Windows 컨테이너와 외부 볼륨 드라이버도 실행하지 않았다. 같은 경로 누락 오류라면 볼륨을 삭제해 다시 만드는 대신, 해당 볼륨 안에 필요한 디렉터리가 준비됐는지와 초기화가 먼저 끝났는지를 확인한다.