[개발 깨알 상식_Tips] / 경로 오타를 빈 디렉터리로 만든 Docker 바인드 마운트.md

경로 오타를 빈 디렉터리로 만든 Docker 바인드 마운트

조회

2026년 7월 14일 | 개발 깨알 상식_Tips


/srv/app/config 대신 /srv/app/confg라고 한 글자를 빼먹었는데 컨테이너는 정상 실행됐다. 애플리케이션도 즉시 죽지 않았다. 다만 설정 파일을 찾지 못해 내장 기본값으로 떠 있었고, 나는 로그의 다른 경고부터 한참 뒤졌다. 실패해야 할 입력이 빈 디렉터리로 바뀌면서 경로 오타가 정상 부팅처럼 보인 것이 문제였다.

원인은 Docker의 바인드 마운트 문법 차이에 있었다. 짧은 -v 문법은 호스트 source 경로가 없으면 디렉터리를 자동으로 만든다. 반면 --mount type=bind는 기본적으로 source가 없을 때 컨테이너 시작 전에 오류를 낸다. 둘 다 같은 폴더를 연결하는 문법처럼 보여도, 잘못된 경로를 만났을 때의 행동은 같지 않았다.

Docker bind mount에서 잘못된 source 경로가 -v에서는 빈 디렉터리로 생성되고 --mount에서는 시작 전에 실패하는 흐름

Figure 1. source 경로 오타를 자동 생성으로 넘길지, 시작 전 오류로 멈출지가 두 문법에서 갈린다.

그림의 왼쪽은 실제로 존재하지 않는 오타 경로다. -v는 그 자리에 빈 디렉터리를 만든 뒤 컨테이너를 시작할 수 있고, --mount는 source가 없다는 오류로 시작을 막는다. 내가 원한 것은 편한 자동 생성이 아니라, 설정 경로가 틀렸을 때 바로 보이는 실패였다.

빈 폴더가 설정 누락을 가렸다

문제가 난 서비스는 /app/config 아래의 YAML 파일을 읽도록 되어 있었다. 오타 난 호스트 경로가 빈 디렉터리로 만들어지자 컨테이너 안의 마운트 지점도 비었다. 애플리케이션은 설정 파일이 없으면 개발용 기본값을 쓰는 구조라 프로세스는 살아남았다. 상태 확인 엔드포인트도 200을 반환했다.

겉으로는 Docker 실행, 프로세스 시작, 상태 확인이 모두 통과했다. 하지만 실제로 확인해야 할 것은 원하는 설정이 로드됐는가였다. 마운트 자체의 성공과 설정 적용의 성공을 같은 신호로 읽은 탓에 원인이 멀리 밀렸다. 자동 생성된 디렉터리는 오류를 고친 것이 아니라 실패 위치를 뒤로 옮겼다.

이런 착시는 설정 파일만의 문제가 아니다. 모델 가중치, 테스트 fixture, 빌드 결과를 호스트에서 넣을 때도 빈 source가 생기면 컨테이너는 예상보다 늦게 실패할 수 있다. 프로그램이 fallback을 갖고 있으면 아예 실패하지 않고 다른 데이터로 실행될 수도 있다. 그래서 컨테이너가 떴다는 사실만으로 bind mount 입력을 검증했다고 보지 않는다.

-v--mount의 기본값 차이

Docker 공식 문서 기준으로 --volume, 즉 -v를 사용한 bind mount는 호스트 경로가 없을 때 그 위치에 디렉터리를 만든다. 파일을 기대한 경로여도 생성되는 것은 디렉터리다. 짧고 익숙한 문법이지만, source의 존재 여부를 엄격하게 확인하는 장치로는 부족하다.

docker run --rm   -v /srv/app/confg:/app/config:ro   example-worker

반대로 아래처럼 --mount type=bind를 쓰면 source가 존재하지 않을 때 기본 동작은 오류다. sourcetarget 이름도 명시되어 있어서 어느 쪽이 호스트인지 다시 읽기 쉽다. 설정·가중치처럼 반드시 미리 존재해야 하는 입력에는 이쪽이 내가 기대한 계약에 더 가까웠다.

docker run --rm   --mount type=bind,source=/srv/app/confg,target=/app/config,readonly   example-worker

최근 Docker 문서에는 bind-create-src--mount에서도 source 디렉터리를 의도적으로 만들 수 있는 선택지가 적혀 있다. 하지만 기존 입력을 읽어야 하는 작업이라면 굳이 켜지 않는다. 생성이 필요한 출력 폴더와 존재해야 하는 입력 폴더를 같은 정책으로 묶지 않는 편이 낫다.

파일 마운트에서는 타입까지 확인했다

더 헷갈리는 경우는 파일 하나를 source로 줄 때다. 예를 들어 /srv/app/config.yaml을 컨테이너의 같은 파일 위치에 연결하려는데 호스트 파일이 없다면, -v는 호스트 쪽에 config.yaml이라는 이름의 디렉터리를 만들 수 있다. 이름은 파일처럼 보이지만 실제 타입은 디렉터리다.

그 뒤에는 “디렉터리를 파일 위에 마운트하려는가” 같은 오류가 나거나, 생성된 디렉터리가 다음 재시도까지 남아 원인을 더 흐릴 수 있다. 파일을 새로 만들어도 같은 이름의 디렉터리가 이미 있어서 저장이 실패한다. 이때는 컨테이너 로그만 지우고 다시 돌리지 않고, 호스트 경로의 타입과 생성 시각부터 확인해야 한다.

나는 실행 전에 디렉터리 입력은 test -d, 파일 입력은 test -f로 나눠 검사한다. Linux에서 경로 구성요소 전체가 실제로 존재하는지 엄격하게 보려면 realpath -e도 보조로 쓴다. 단순히 “무언가 존재한다”가 아니라 기대 타입으로 존재한다가 통과 조건이다.

test -d /srv/app/config || exit 21
realpath -e /srv/app/config

test -f /srv/app/config.yaml || exit 22
realpath -e /srv/app/config.yaml

읽기 전용 옵션은 경로 검증이 아니었다

:roreadonly를 붙였다는 이유로 안전하다고 생각하기 쉽다. 읽기 전용은 컨테이너가 마운트된 내용을 수정하지 못하게 하는 권한 경계다. 하지만 -v가 잘못된 source 경로를 호스트에 디렉터리로 만드는 동작 자체를 막아 주는 존재 검사는 아니다.

권한과 경로는 따로 본다. source가 정확한지, 파일과 디렉터리 중 기대한 타입인지, 실제 경로가 심볼릭 링크를 따라 어디를 가리키는지 확인한 뒤 읽기 전용을 붙인다. 하나의 ro 표시에 이 세 검증을 대신 맡기면 “수정은 못 하지만 잘못된 빈 폴더를 읽는” 상태가 남는다.

원격 Docker daemon을 쓸 때는 source가 CLI를 실행한 내 노트북이 아니라 daemon이 실행되는 호스트 기준이라는 점도 확인한다. 로컬에 경로가 있어도 원격 daemon 쪽에는 없을 수 있다. 경로 문자열이 맞는지만 보지 않고 어느 머신의 파일시스템을 검사했는가까지 로그에 남겨야 재현이 된다.

입력과 출력의 생성 정책을 분리했다

지금은 bind mount를 두 종류로 나눈다. 설정, 인증서, 모델, fixture처럼 이미 있어야 하는 입력은 source 미존재를 즉시 실패시킨다. 로그, 캐시, export처럼 컨테이너가 만들 출력은 사전에 디렉터리를 명시적으로 생성하고 소유권과 남은 공간을 확인한 뒤 연결한다. Docker가 우연히 만들어 준 폴더를 정상 준비 단계로 취급하지 않는다.

  • 입력 source는 실행 전에 존재 여부와 파일 타입을 확인한다.
  • 실패가 필요한 입력에는 기본적으로 --mount type=bind를 쓴다.
  • 출력 디렉터리는 별도 준비 단계에서 만들고 권한을 확인한다.
  • 상태 확인에는 프로세스 생존뿐 아니라 설정·모델 로드 신호를 포함한다.

내가 찾던 해결책은 더 긴 Docker 명령이 아니었다. 경로가 틀렸을 때 빈 폴더를 만들어 계속 가는 대신, 컨테이너가 뜨기 전에 멈추는 기본값이었다. source의 존재와 타입을 먼저 확인하고 입력에는 fail-fast 문법을 쓰니, 설정 누락이 애플리케이션의 이상한 기본 동작으로 번지는 일이 줄었다.

비슷한 증상이 보이면 컨테이너 안쪽부터 뒤지지 않는다. 호스트에 낯선 빈 디렉터리가 새로 생겼는지, 그 이름이 원래 파일이었는지, 실행 명령이 -v였는지부터 본다. 이 세 가지를 확인하면 “컨테이너는 떴는데 내용이 없다”는 문제를 마운트 경계에서 훨씬 짧게 닫을 수 있다.

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.