AI 프로젝트 글/디버깅

Docker 컨테이너는 실행되는데 Synology 공유 폴더를 읽지 못한 이유

바로퇴장 2026. 8. 18. 00:40

결론

컨테이너가 정상 실행됐지만 사진 공유 폴더를 읽지 못한 근본 원인은 컨테이너의 임의 UID/GID와 Synology DSM에서 읽기 권한을 받은 서비스 계정의 UID/GID가 일치하지 않았기 때문이었다.

DSM 마스터 계정이나 root로 실행하지 않고, 다음 방식으로 해결했다.

  1. 관리자 그룹에 속하지 않은 DSM 전용 서비스 계정을 만든다.
  2. 테스트 사진 공유 폴더에만 읽기 권한을 준다.
  3. 전용 계정의 실제 UID/GID를 확인한다.
  4. 그 값을 Compose의 컨테이너 실행 사용자에 매핑한다.
  5. 읽기 성공과 쓰기 실패를 각각 재검증한다.

핵심은 “Permission denied를 없애는 것”이 아니라 “필요한 사용자만 읽고 누구도 이 경로를 쓰지 못하는 상태”를 만드는 것이었다.

현상과 영향

Photo MCP 컨테이너는 시작됐고 HTTP 상태 확인도 성공했다. 하지만 MCP가 마운트된 사진 디렉터리의 목록을 읽으려고 하면 권한 오류가 발생했다.

이 상태에서는 다음과 같은 착시가 생긴다.

  • 컨테이너 상태: 정상
  • HTTP 서버: 정상
  • 사진 볼륨 마운트: 설정상 존재
  • 실제 서비스 기능: 사진을 읽지 못해 실패

즉, 상태 확인만 보면 서비스가 정상처럼 보이지만 핵심 데이터 접근 기능은 사용할 수 없었다.

환경과 재현 조건

  • Synology NAS와 DSM Container Manager
  • Python 기반 MCP 서버
  • 호스트 사진 폴더를 /data/photos:ro로 bind mount
  • 컨테이너는 비루트 UID로 실행
  • DSM 공유 폴더는 별도 서비스 계정에 읽기 권한 부여

최초 이미지에는 일반적인 비루트 실행을 위해 임의 UID가 정의돼 있었다. 그러나 그 UID는 DSM에서 사진 폴더 읽기 권한을 받은 계정의 숫자 ID가 아니었다.

관찰한 증거

문제를 애플리케이션 오류로 단정하지 않고 계층별로 나눠 확인했다.

확인 대상관찰 결과의미

컨테이너 프로세스 실행 중 이미지와 시작 명령은 동작
/health HTTP 200 HTTP 서버와 포트는 동작
사진 마운트 설정 존재 Compose가 경로를 연결함
컨테이너 실행 신분 임의 비루트 UID/GID DSM 전용 계정과 다른 신분
사진 목록 읽기 권한 거부 파일 인가 계층 문제 가능성 증가

/health가 성공했다는 사실은 애플리케이션 프로세스가 살아 있다는 증거다. 마운트된 데이터의 읽기 권한까지 보장하는 증거는 아니다.

세운 가설

가설 1. 호스트 경로가 잘못됐다

사진 폴더가 다른 경로에 마운트됐다면 파일이 없거나 디렉터리를 찾지 못할 수 있다. Compose 설정과 컨테이너의 마운트 지점을 확인했다.

결과: 마운트 자체는 존재했으므로 주원인이 아니었다.

가설 2. :ro가 읽기까지 막았다

읽기 전용 마운트라는 이름 때문에 읽기도 막는다고 오해할 수 있다. 하지만 :ro는 쓰기를 차단하는 옵션이며 기존 읽기 권한을 새로 부여하거나 제거하는 기능이 아니다.

결과: 읽기 권한 거부의 직접 원인이 아니었다.

가설 3. MCP 서버의 경로 검증 코드가 차단했다

서버는 허용된 루트 밖으로 나가는 경로를 거부한다. 잘못된 상대 경로나 확장자를 전달했다면 애플리케이션 수준 오류가 발생할 수 있다.

결과: 사진 루트 자체의 목록 읽기 단계에서 OS 권한 오류가 발생했기 때문에 주원인이 아니었다.

가설 4. 컨테이너 UID/GID와 DSM ACL이 일치하지 않았다

컨테이너 내부 프로세스의 숫자 신분과 DSM에서 권한을 받은 계정의 숫자 신분을 비교했다.

결과: 두 값이 달랐고, 이 가설이 실제 원인과 일치했다.

진단 과정

1. 서비스 상태와 데이터 접근을 분리했다

먼저 HTTP 상태 확인으로 애플리케이션 시작 문제를 제외했다. 그다음 사진 디렉터리 목록 읽기를 별도 테스트해 데이터 접근 문제임을 좁혔다.

2. 컨테이너의 실행 신분을 확인했다

컨테이너 내부에서 id로 UID/GID를 확인했다. 사용자 이름보다 숫자 값이 중요하다.

id

3. DSM 서비스 계정의 숫자 ID와 비교했다

DSM에서 테스트 폴더 읽기 권한을 받은 전용 계정의 실제 UID/GID를 확인했다. 진단 단계에서 필요한 계정 정보를 읽기 전용으로 확인했으며, 확인을 위해 잠시 사용한 진단용 시스템 파일 마운트는 최종 구성에서 제거했다.

4. 권한 문제와 애플리케이션 문제를 구분했다

동일한 경로에 대해 운영체제 수준의 목록 읽기가 실패하면 MCP 도구 코드보다 파일 권한을 먼저 봐야 한다. 반대로 OS 수준 읽기는 성공하는데 도구만 실패하면 경로 검증과 이미지 처리 코드를 확인해야 한다.

이 순서 덕분에 코드 수정을 반복하지 않고 권한 계층으로 원인을 좁힐 수 있었다.

근본 원인

컨테이너 이미지에는 보안을 위해 임의의 비루트 사용자가 정의돼 있었다. 비루트라는 방향은 맞았지만, 그 숫자 UID/GID는 Synology DSM의 공유 폴더 ACL에서 읽기 권한을 받은 서비스 계정과 연결되지 않았다.

bind mount는 컨테이너에 경로를 보여줄 뿐, 호스트 파일시스템 권한을 무시하지 않는다. 따라서 컨테이너 프로세스는 DSM 입장에서 권한을 받지 않은 숫자 신분으로 사진 폴더에 접근했고, 읽기가 거부됐다.

정리하면 다음과 같다.

컨테이너 실행 성공 ≠ 마운트 성공 ≠ 파일 읽기 권한 성공

세 상태는 각각 검증해야 한다.

수정 내용

1. DSM 전용 서비스 계정 생성

마스터 계정을 재사용하지 않고 사진 MCP 전용 계정을 만들었다. 관리자 그룹과 DSM 응용 프로그램 접근은 제외하고, 테스트 사진 공유 폴더에만 읽기 권한을 부여했다.

2. 실제 UID/GID를 Compose 변수로 분리

services:
  photo-mcp:
    user: "${MCP_RUN_UID}:${MCP_RUN_GID}"

NAS마다 UID/GID가 다를 수 있으므로 값을 소스 코드에 고정하지 않고 배포 환경의 .env에서 주입하도록 했다. 공개 문서에는 실제 숫자를 기록하지 않는다.

3. 읽기 전용 방어 유지

사용자 매핑을 수정했지만 다음 보안 설정은 그대로 유지했다.

  • 사진 볼륨 :ro
  • 컨테이너 루트 파일시스템 read_only: true
  • Linux capability 전체 제거
  • no-new-privileges
  • 쓰기 MCP 도구 미제공

읽기 권한 문제를 해결한다는 이유로 root 실행이나 전체 권한 부여로 돌아가지 않았다.

재검증 결과

수정 후 다음 순서로 검증했다.

검증결과

컨테이너 실행 UID/GID DSM 전용 계정과 대응
사진 디렉터리 목록 읽기 성공
사진 파일 생성 시도 읽기 전용 파일시스템으로 실패
/health HTTP 200
토큰 없는 /mcp HTTP 401
사진 목록 MCP 도구 테스트 사진 2장 반환
임시 진단 마운트 최종 구성에서 제거

중요한 점은 읽기 성공만 확인하지 않은 것이다. 권한 변경 과정에서 쓰기까지 가능해지지 않았는지 파일 생성 실패를 별도로 확인했다.

실패했던 접근과 이유

임의 비루트 UID만 사용

보안 관점에서 root를 피했다는 점은 맞았지만, DSM ACL과 연결되지 않은 UID였기 때문에 실제 데이터 읽기는 불가능했다.

root 실행

권한 오류를 빠르게 우회할 수 있지만 최종 해결책으로 채택하지 않았다. 컨테이너가 침해되면 NAS 데이터와 호스트에 미치는 영향이 커질 수 있다.

공유 폴더 전체 권한 부여

chmod 777처럼 광범위한 권한 부여도 사용하지 않았다. 문제를 가리는 대신 공격 표면을 넓히며, 어떤 계정이 실제로 필요한 권한을 가졌는지 설명하기 어려워진다.

읽기 성공만 테스트

읽기 성공만으로 완료하면 UID/GID 수정 과정에서 쓰기 권한까지 열렸는지 놓칠 수 있다. 그래서 READ_OK와 WRITE_BLOCKED를 별도의 성공 기준으로 정했다.

재발 방지

  1. NAS 배포 전에 전용 서비스 계정과 허용 폴더를 먼저 정의한다.
  2. 다른 NAS로 이동할 때 UID/GID를 그대로 복사하지 않고 다시 확인한다.
  3. Compose에는 숫자를 직접 고정하지 않고 환경변수로 주입한다.
  4. 배포 검증표에 실행 신분, 읽기 성공, 쓰기 실패를 모두 포함한다.
  5. 상태 확인은 프로세스 상태와 데이터 접근 상태를 구분한다.
  6. 진단을 위해 추가한 마운트나 권한은 원인 확인 후 제거한다.
  7. root 또는 전체 권한은 최종 해결책으로 남기지 않는다.

관련 CS 개념

이번 문제의 핵심인 UID, GID, ACL과 bind mount 권한의 관계는 별도 글에서 자세히 설명한다.

관련 프로젝트