mLog 명판이 있는 제본 작업대에서 종이 한 장을 좁은 제본기에 맞추고 옆의 문서 상자와 구분하는 흰색 3D 유령 제본사

이름이 비슷해도 한 장의 파일과 여러 파일을 담는 디렉터리는 같은 대상이 아니다.

Docker 파일 마운트가 폴더로 생길 때: not a directory와 Compose 경로 진단

서버·인프라 Docker 2026년 10월 27일

default.conf를 연결했는데 같은 이름의 폴더가 생기고, 컨테이너가 not a directory 오류로 시작되지 않는 경우가 있다. 먼저 Docker가 읽는 호스트의 원본 경로가 실제 일반 파일인지 확인해야 한다. 파일 이름이 .conf로 끝난다는 사실만으로 파일 종류가 결정되지는 않는다.

Compose의 짧은 bind mount 문법은 없는 원본 경로를 디렉터리로 만들 수 있다. 설정 파일 누락이 다른 모양의 오류로 이어지는 이유다. 긴 문법의 bind.create_host_path: false로 이 자동 생성을 막을 수 있지만, 이미 존재하는 디렉터리를 파일로 고쳐 주는 옵션은 아니다.

검증 범위: 2026-10-27 Docker 공식 문서를 확인했다. Linux 호스트에서 Docker Compose로 설정 파일을 bind mount하는 구성을 기준으로 한다. Ubuntu 24.04.3 LTS·GNU coreutils 9.4에서 파일 종류와 심볼릭 링크 6가지 경우를 직접 확인했다. 이번 환경에는 Docker·Compose 실행 파일이 없어 컨테이너 시작·마운트·Nginx 설정 검사는 실행하지 않았다. 경로와 서비스 이름은 설명용이며 mLog 서버의 실제 설정이 아니다.

1. 오류의 원본 경로와 대상 경로를 먼저 나눈다

not a directory가 포함된 오류를 보면 다음 세 항목을 적는다.

  • 호스트에서 가져오는 경로: source.
  • 컨테이너 안에 연결하는 경로: target 또는 Destination.
  • 각 위치에서 기대하는 종류: 일반 파일인지 디렉터리인지.

파일을 디렉터리 자리에 놓았는지, 디렉터리를 파일 자리에 놓았는지 양쪽을 확인한다. 호스트 원본뿐 아니라 컨테이너 대상 경로와 그 상위 경로도 점검해야 한다. 단순 권한 오류와 같은 문제라고 가정하고 chmod 777부터 실행하지 않는다.

예를 들어 다음은 기존 web 서비스에서 Nginx 설정 파일 하나를 연결하려는 짧은 문법이다. 완성된 Compose 파일이 아니라 volumes 부분의 예시다.

volumes:
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro

./nginx/default.conf가 없으면 Compose가 그 위치에 디렉터리를 만들 수 있다. Docker CLI의 -v도 없는 bind 원본을 디렉터리로 생성한다. 반면 docker run --mount type=bind는 기본적으로 없는 원본 경로를 오류로 처리한다. 현재 문서에는 자동 생성용 별도 옵션도 있으므로 --mount가 언제나 경로를 만들지 않는다고 일반화하지 않는다. Docker bind mount · Compose volumes 문법

2. 내가 보고 있는 서버가 Docker가 사용하는 서버인지 확인한다

터미널에서 파일을 찾았더라도 Docker가 다른 호스트를 보고 있으면 근거가 되지 않는다.

docker context show
docker context ls
docker compose version
docker compose ps -a

명령은 해당 프로젝트를 운영하던 계정과 Compose 옵션으로 실행한다. -f, -p, --env-file, --context를 사용했다면 계속 같은 조건을 유지한다. context뿐 아니라 DOCKER_HOST 등 연결 대상에 영향을 주는 설정도 대조한다. Docker contexts

bind mount의 원본은 Docker 데몬이 실행되는 호스트에 있어야 한다. 원격 서버를 관리하는 노트북의 로컬 파일을 자동으로 전송하는 기능이 아니다. Docker Desktop은 VM과 호스트 파일 공유를 처리하므로 Linux 원격 호스트와 같은 경로 규칙이라고 단정하지 않는다. bind mount의 호스트 제약

예약 작업에서만 경로가 다르다면 cron에서 Docker Compose가 실패할 때의 계정·실행 환경 점검을 먼저 적용한다.

3. YAML에 적은 경로와 Compose가 해석한 경로를 비교한다

Compose 파일을 여러 개 합치면 상대경로의 기준을 잘못 생각하기 쉽다. 일반적인 여러 -f 구성에서는 첫 Compose 파일이 경로 기준이며, --project-directory로 기준을 바꿀 수 있다. override 파일의 폴더를 무조건 기준으로 삼지 않는다. Compose CLI 경로 규칙

먼저 기존 실행 옵션을 유지한 상태에서 구성을 검사한다.

docker compose config --quiet

다음 예시는 Python 3가 있는 환경에서 web 서비스의 마운트 항목만 출력한다. 서비스 이름은 실제 이름으로 바꾼다.

docker compose config --format json |
python3 -c 'import json,sys
cfg=json.load(sys.stdin)
for m in cfg["services"]["web"].get("volumes", []):
    print(json.dumps(m, ensure_ascii=False))'

type, source, target, read_only를 확인한다. 전체 config 출력에는 환경 변수 등 민감한 값이 포함될 수 있으므로 그대로 외부에 붙여 넣지 않는다. 마운트 경로도 공유 전에 검토한다.

config --quiet 성공은 구성 검사 결과다. 실제 호스트에 필요한 설정 파일이 있고, 컨테이너가 그것을 읽을 수 있으며, 설정 내용까지 올바르다는 보증으로 사용하지 않는다. config 공식 설명

4. 확장자 대신 파일 종류를 검사한다

확인한 source 절대경로를 Docker 호스트에서 점검한다. 아래 값은 예시다. 파일 내용을 출력할 필요는 없다.

task_source=/srv/example-stack/nginx/default.conf

ls -ld -- "$task_source"
readlink -e -- "$task_source"

if /usr/bin/test -f "$task_source"; then
  printf 'regular file\n'
elif /usr/bin/test -d "$task_source"; then
  printf 'directory: check source type\n'
else
  printf 'missing, broken link, other type, or inaccessible\n'
fi

검사 옵션의 역할은 다르다.

  • -e: 존재하는지 확인한다. 일반 파일만을 뜻하지 않는다.
  • -f: 링크를 따라간 대상이 일반 파일인지 확인한다.
  • -d: 디렉터리인지 확인한다.
  • -L: 경로 자체가 심볼릭 링크인지 확인한다.
  • -s: 크기가 0보다 큰지 확인한다. 파일 종류나 설정 내용의 유효성을 보장하지 않는다.

readlink -e는 경로 구성 요소가 모두 존재해야 정규화된 경로를 출력한다. 링크를 썼다면 최종 목적지도 점검한다. 접근 권한이 부족한 계정의 실패 결과를 파일 부재로 확정하지 않는다. GNU test 매뉴얼 · GNU readlink 매뉴얼

이번 로컬 실험에서는 .conf라는 이름의 디렉터리도 -e가 참이었다. 정상 파일을 가리키는 심볼릭 링크는 -f가 참이었지만, 끊어진 링크는 -L만 참이었다. 빈 파일 역시 -f는 참이었다. 따라서 touch로 빈 파일을 만드는 것만으로 설정 복구가 끝났다고 판단할 수 없다.

5. 원본이 없을 때는 조용히 폴더를 만들지 않게 한다

설정 파일이 반드시 준비돼 있어야 하는 서비스라면 긴 bind 문법을 사용한다. 아래는 기존 서비스에 반영할 항목만 보여 준다. 기존 이미지·포트·다른 마운트를 누락시키지 않는다.

services:
  web:
    # 기존 image, ports 및 다른 설정 유지
    volumes:
      - type: bind
        source: ./nginx/default.conf
        target: /etc/nginx/conf.d/default.conf
        read_only: true
        bind:
          create_host_path: false

create_host_path: false는 없는 원본을 디렉터리로 자동 생성하지 않도록 하는 설정이다. 이미 디렉터리가 있으면 “반드시 일반 파일이어야 한다”는 조건까지 검사해 주지는 않는다. 앞 절의 종류 검사가 따로 필요하다. read_only도 컨테이너의 쓰기를 제한하는 설정이지, 빠진 원본을 보완하는 설정은 아니다. Compose 긴 volumes 문법

이미 잘못 생성된 폴더를 발견했다면 곧바로 지우지 않는다. 다른 서비스가 사용하는지, 내부에 데이터가 생겼는지 먼저 살핀다. 필요하면 검증된 설정 파일을 별도 경로에 복원하고 source를 그 경로로 바꾸는 방식으로 범위를 좁힌다. 빈 파일이나 임의의 샘플 설정으로 운영 설정을 덮어쓰지 않는다.

DB 데이터 경로에는 이 예시를 그대로 적용하지 않는다. 설정 파일의 종류 오류를 고치려는 작업에서 docker compose down -v, docker volume prune, 데이터 디렉터리 삭제는 필요하지 않다. 백업을 복구 근거로 사용할 때는 Ghost·MySQL 백업 무결성 점검처럼 파일 존재와 복원 가능성을 구분한다.

6. 설정 반영 후 실제 마운트와 애플리케이션을 따로 확인한다

컨테이너가 만들어져 있다면 docker compose ps -a에서 확인한 ID로 마운트 정보만 읽는다.

task_container=확인한_컨테이너_ID
docker container inspect "$task_container" \
  --format '{{json .Mounts}}'

Source와 Destination, Type, RW를 새 설정과 대조한다. 아직 컨테이너가 생성되지 않았다면 inspect할 대상이 없을 수 있다. 이때는 Compose를 실행했을 때의 오류와 해석된 설정부터 확인한다. 전체 inspect 출력 역시 환경 변수 등 민감한 값을 포함할 수 있다. container inspect

마운트 선언을 바꾼 뒤 docker compose restart만 실행하면 Compose 파일 변경이 적용되지 않는다. restart와 재생성의 차이를 기준으로 변경한 서비스를 적용한다.

다음은 source 파일과 내용, 의존 서비스 상태를 확인한 뒤 대상 서비스를 중단·재생성해도 되는 시간에 사용하는 예시다. 읽기 전용 진단 명령이 아니다.

# web은 실제 서비스 이름으로 교체
docker compose up -d --no-deps --force-recreate web
docker compose ps -a web
docker compose logs --tail 100 web

--no-deps는 의존 서비스를 시작하지 않으므로 필요한 서비스가 이미 동작할 때 사용한다. --force-recreate는 대상 컨테이너를 다시 만들며 컨테이너 쓰기 계층의 데이터는 별도 보존 대상이다. 원본 파일·기존 Compose·필요한 데이터를 확보하고 변경 전후 마운트 경로를 비교한다. Compose up · Compose restart

마운트가 성공해도 설정 문법은 틀릴 수 있다. Nginx 서비스가 실제 실행 중이라면 다음으로 검사할 수 있다. 다른 애플리케이션에는 해당 제품의 검사 명령을 사용한다.

docker compose exec -T web nginx -t

Nginx의 -t는 문법을 검사하고 설정에서 참조한 파일을 열어 본다. 실제 요청 처리까지 보장하지 않으므로 마지막에는 기대한 URL 응답을 확인한다. 서비스가 시작되지 못했다면 위 exec 명령을 반복하지 말고 시작 오류를 확인하고 별도 검증 환경에서 설정을 검사한다. Nginx 명령 옵션

컨테이너는 실행되는데 프록시에서만 실패한다면 Nginx Proxy Manager 502 진단의 네트워크·포트 단계로 넘어간다.

7. 컨테이너 안의 기존 파일이 사라진 것처럼 보일 때

폴더 전체를 마운트하면 이미지에 있던 해당 위치의 기존 내용이 가려질 수 있다. 원본 파일이 삭제됐다는 뜻으로 단정하지 않는다. 설정 파일 하나를 연결하려다가 빈 호스트 폴더를 설정 디렉터리 전체에 연결했는지 확인한다. 기존 데이터 위의 bind mount

최종 확인은 세 가지로 나눈다. 의도한 Docker 호스트의 원본이 올바른 종류인지, 컨테이너가 그 경로를 실제로 마운트했는지, 애플리케이션이 설정을 읽고 기대한 응답을 내는지 확인한다. 이름과 파일 존재만 맞는 상태에서 멈추지 않으면 같은 오류가 다시 나타나는 이유를 더 정확하게 찾을 수 있다.

태그

mLog

웹 개발과 서버 운영 과정에서 마주친 문제와 해결 과정을 기록합니다. 직접 확인한 설정과 실행 결과를 함께 정리해, 비슷한 문제를 겪는 분들이 참고할 수 있도록 합니다.