Docker bind mount 권한 충돌 — 호스트 ↔ 컨테이너 UID 한 줄 진단 패턴

문제 상황 — bind mount 폴더에 컨테이너가 쓰지 못한다

Docker 컨테이너에 호스트 폴더를 bind mount로 붙였는데, 컨테이너 안에서 그 경로에 파일을 쓰려고 하면 Permission denied가 떨어지는 상황은 워드프레스 MCP 1같은 SQLite 기반 서비스를 운영하다 보면 한 번쯤 마주치게 된다. 본인도 wordpress-mcp 2에 OAuth 2.1 AS를 붙이며 <folder_path>/data 3폴더에 SQLite 파일(oauth.db)을 만들어야 하는 단계에서 같은 벽에 부딪혔다.

원인은 거의 항상 한 가지로 수렴한다 — 호스트 측 폴더의 owner UID와 컨테이너 내부 프로세스의 UID가 다르기 때문이다. Linux의 권한 검사는 이름이 아니라 UID 숫자로 진행되므로, 호스트의 <user> 사용자(UID 1000)와 컨테이너 안의 appuser(UID 1000)가 같은 숫자라야 비로소 같은 사용자로 취급된다.

한 줄 진단 패턴 3종

구글링이든 AI 채팅이든 이것저것 다 찾아보고 물어보다가 결국 찾아낸, 이 상황을 빠르게 가르는 명령 세 줄을 묶어 두었다. 다른 컨테이너에서도 동일하게 재사용 가능한 보편적 진단 패턴이다.

# ① 호스트 측: bind mount될 폴더의 owner uid:gid 확인
#   - stat 명령의 -c 옵션은 출력 포맷을 직접 지정한다
#   - %u = owner UID(숫자), %g = group GID(숫자)
#   - 이름이 아니라 숫자로 봐야 컨테이너 측과 직접 비교가 가능하다
stat -c "%u:%g" <folder_path>/data

# ② 컨테이너 측: 컨테이너 프로세스가 도는 사용자의 uid 확인
#   - docker compose exec <service>: 실행 중인 컨테이너에 명령을 주입한다
#   - id 명령은 현재 프로세스의 uid / gid / groups 를 출력한다
#   - Dockerfile USER 지시자가 결국 어떤 UID로 풀리는지 실증한다
docker compose exec <container> id

# ③ 두 UID가 다르면 호스트 측을 컨테이너 UID에 맞춰 정렬한다
#   - -R 옵션으로 하위 폴더·파일까지 재귀 적용
#   - owner와 group을 동시에 컨테이너 측 숫자로 통일
sudo chown -R 1000:1000 <folder_path>/data

각 줄을 풀어 보면 이렇다.

stat -c "%u:%g"는 폴더의 소유자를 숫자 UID:GID 형태로 찍어 준다. ls -l이 보여 주는 사용자 이름(<user>, root)은 호스트의 /etc/passwd로 해석된 결과일 뿐이고, 컨테이너 안에는 별도의 /etc/passwd가 있다. 같은 이름이라도 UID 숫자가 다르면 남남이고, 이름이 달라도 UID가 같으면 동일 사용자로 취급된다. 진단의 첫 단계는 반드시 숫자로 보는 것이다.

docker compose exec <service> id는 컨테이너 내부에서 현재 프로세스가 어떤 UID로 도는지 확인하는 가장 짧은 방법이다. Dockerfile에 USER appuser가 있더라도, 그 appuser가 결국 어느 숫자에 매핑되는지는 실행해 보기 전에는 알 수 없다. 이 한 줄로 추정 대신 사실을 확보한다.

마지막 chown은 결과적 처치다. 두 UID가 어긋날 때 호스트 측을 컨테이너 측에 맞추는 방향이 가장 무난하다. 반대 방향(Dockerfile에서 UID를 바꾸는 것)은 이미지 재빌드를 동반하므로 운영 중인 서비스에는 비용이 크다.

왜 이 패턴이 보편적으로 통하는가(이하 AI 대답 요약)

bind mount의 본질은 호스트 inode를 컨테이너 네임스페이스에 그대로 노출하는 것이다. 파일시스템 레벨의 권한 검사는 컨테이너 안이든 밖이든 동일한 커널이 수행하고, 그 검사는 UID/GID 숫자만 본다. 그래서 도커가 “권한을 적당히 넘겨주는” 마법은 일어나지 않는다. SQLite 같은 stateful 데이터를 호스트의 폴더에 두기로 결정한 순간, 호스트 폴더의 UID가 컨테이너 측과 같아야 한다는 제약은 계약처럼 따라온다.

Volume(named volume) 방식은 이 문제가 덜한데, 도커가 볼륨을 처음 만들 때 컨테이너 내 mount point의 권한을 자동으로 복사해 주기 때문이다. 그러나 bind mount는 호스트가 이미 가지고 있는 폴더에 컨테이너를 강제로 붙이는 구조라, 도커가 손댈 여지가 없다. 둘의 차이는 단순한 취향이 아니라 권한 자동 정렬 여부라는 운영상의 실질 차이다.

한 줄 정리

bind mount의 권한 문제는 추측으로 풀 게 아니라 stat -c "%u:%g"docker compose exec id 두 줄로 사실을 먼저 확보한 뒤 chown으로 정렬하는 것이다.

조회수: 3

Footnotes

  1. 본인 블로그에 조금 편하게 사용하려고 vibe coding으로 만든 MCP.
  2. vibe coding으로 만들어 명명한 MCP 이름이자 <container>.
  3. 본인의 경우 /opt/wordpress-mcp/data 폴더.

댓글 남기기