Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
 

(시리즈 글이 2개 있습니다.)
Linux: 127. "USER ..." 설정이 된 Docker 컨테이너의 호스트 측 볼륨 권한
; https://www.sysnet.pe.kr/2/0/14030

Linux: 128. "USER ..." 설정이 된 Docker 컨테이너의 호스트 측 볼륨 권한 (2)
; https://www.sysnet.pe.kr/2/0/14031




"USER ..." 설정이 된 Docker 컨테이너의 호스트 측 볼륨 권한

테스트를 위해 다음과 같은 Dockerfile을 작성 후,

$ echo ${USER}
testusr

$ echo "FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base" > Dockerfile

컨테이너를 빌드한 다음, volume을 매핑해 실행해 봅니다.

$ docker build -t dotnet_test .

$ docker run --rm -it -v dotnet_test_dir:/app --name dotnet_test_inst dotnet_test /bin/bash

컨테이너 내부에서 sleep 명령어를 실행한 후,

// dotnet_test_inst 컨테이너 내부에서 실행

root@fecc129e121d:/# echo "TEST" > /app/test.txt

root@fecc129e121d:/# sleep 200

호스팅하는 리눅스 서버에서 해당 프로세스를 확인해 보면 root 권한으로 실행 중임을 알 수 있습니다.

// docker 호스팅 서버에서 실행

$ ps aux | grep " sleep 200"
root     2950631  0.0  0.0   2492  1304 pts/0    S+   13:15   0:00 sleep 200

그렇다면 당연히 매핑된 volume도 root 소유권이 됩니다.

// docker 호스팅 서버에서 실행

$ docker volume inspect dotnet_test_dir
[
    {
        "CreatedAt": "2025-10-11T04:15:18Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/mnt/docker-data/docker/volumes/dotnet_test_dir/_data",
        "Name": "dotnet_test_dir",
        "Options": null,
        "Scope": "local"
    }
]

$ ls -la /mnt/docker-data/docker/volumes/dotnet_test_dir/_data      
total 4
drwxr-xr-x 2 root root 30 Oct 11 13:43 .
drwx-----x 3 root root 27 Oct 11 13:15 ..
-rw-r--r-- 1 root root  5 Oct 11 13:43 test.txt

$ cat /mnt/docker-data/docker/volumes/dotnet_test_dir/_data/test.txt
TEST

$ sudo ls -la /mnt/docker-data/docker/volumes/ | grep dotnet_test_dir
drwx-----x   3 root     root     27 Oct 11 13:15 dotnet_test_dir

$ sudo ls -la /mnt/docker-data/docker/volumes/dotnet_test_dir
total 24
drwx-----x   3 root root     27 Oct 11 13:15 .
drwxrwxr-x 238 root test  20480 Oct 11 13:15 ..
drwxr-xr-x   2 root root      6 Oct 11 13:15 _data

그리고 root 이외의 사용자/그룹에 대해서는 'w' 쓰기 권한이 없습니다.




저렇게 생성한 volume을 그대로 두고, Dockerfile을 "User app"으로 시작하도록 수정해 보겠습니다.

// runtime:8.0 이미지에는 "app"이라는 비루트 사용자가 미리 정의돼 있음

$ echo -e "FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base\nUSER app" > app.Dockerfile

$ docker build -f app.Dockerfile -t dotnet_test_non_root .

우선 볼륨 매핑 없이 컨테이너를 실행해 볼까요?

$ docker run -it --name dotnet_test_non_root_inst dotnet_test_non_root /bin/bash

app@4638f8e580ed:/$ whoami
app

app@4638f8e580ed:/$ id -u
1654

app@4638f8e580ed:/$ echo $UID
1654

app@4638f8e580ed:/$ id
uid=1654(app) gid=1654(app) groups=1654(app)

app@4638f8e580ed:/$ sleep 200

그럼 저 프로세스(sleep 200)의 소유권을 docker 호스팅 서버에서 확인해 보면,

$ ps aux | grep " sleep 200"
1654     2954740  0.0  0.0   2492  1316 pts/0    S+   13:25   0:00 sleep 200

이전에 "root"로 나왔던 사용자 칼럼의 값이 저렇게 번호(위의 경우 1654)로 나옵니다. 그러니까, 컨테이너를 위해 격리된 user namespace 내에서만 존재하는 사용자이기 때문에 호스팅 서버에서는 저렇게 UID(사용자 ID)로만 표시되는 것입니다.

그렇다면 이제 이전에 매핑했던 볼륨을 "USER ..."로 실행한 컨테이너 내에서 접근하면 어떻게 될까요?

$ docker run --rm -it -v dotnet_test_dir:/app --name dotnet_test_non_root_inst dotnet_test_non_root /bin/bash

app@66d0fca6897b:/app$ ls -la /app
total 4
drwxr-xr-x 2 root root 30 Oct 11 04:43 .
drwxr-xr-x 1 root root 25 Oct 11 04:46 ..
-rw-r--r-- 1 root root  5 Oct 11 04:43 test.txt

app@66d0fca6897b:/app$ cat /app/test.txt
TEST

app@66d0fca6897b:/app$ echo "TEST2" > test2.txt"
bash: test2.txt: Permission denied

app@66d0fca6897b:/app$ mkdir test
mkdir: cannot create directory 'test': Permission denied

보는 바와 같이, "USER ..." 설정의 추가로 인해 기존에 생성해 두었던 볼륨을 읽기 전용으로만 접근할 수 있게 되었습니다. 따라서, 새롭게 변경한 환경에서 기존 볼륨을 사용하고 싶다면 호스팅 서버에서 해당 볼륨의 소유권을 적절하게 변경해 주어야 합니다.




그렇다면 궁금해지는군요. 처음부터 "USER ..." 설정을 사용한 docker 컨테이너에 볼륨이 매핑되었다면 해당 볼륨의 소유권은 어떻게 될까요?

$ docker run --rm -it -v dotnet_test_dir2:/app --name dotnet_test_non_root_inst2 dotnet_test_non_root /bin/bash

// docker 호스팅 서버에서 실행

$ docker volume inspect dotnet_test_dir2
...[생략]...

$ ls -la /mnt/docker-data/docker/volumes/dotnet_test_dir2/_data
total 0
drwxr-xr-x 2 root root 10 Oct 11 13:54 .
drwx-----x 3 root root 27 Oct 11 13:54 ..

오호~~~, root로 나옵니다. 그렇다면 저렇게 "USER ..." 설정이 된 컨테이너에서는 해당 볼륨에 대해 쓰기 권한이 없다는 건데요, 실제로 컨테이너 내부에서 시도해 보면,

$ docker run --rm -it -v dotnet_test_dir2:/app --name dotnet_test_non_root_inst2 dotnet_test_non_root /bin/bash

app@813e761de7e4:/$ id
uid=1654(app) gid=1654(app) groups=1654(app)

app@813e761de7e4:/$ cd /app

app@813e761de7e4:/app$ echo "TEST" > test.txt
bash: test.txt: Permission denied

app@813e761de7e4:/app$ mkdir test
mkdir: cannot create directory 'test': Permission denied

쓰기가 안 됩니다. 이에 대해 검색해 보면,

How to give non-root user in Docker container access to a volume mounted on the host
; https://stackoverflow.com/questions/39397548/how-to-give-non-root-user-in-docker-container-access-to-a-volume-mounted-on-the

대략 해법으로 제공되는 것이 3가지 정도인데요, 첫 번째는 "USER ..." 설정을 하지 말고 컨테이너를 기존처럼 실행한 다음 내부에서 "exec runuser -u ..." 명령어로 프로그램을 (제한된 권한으로) 실행하도록 바꾸는 것입니다. 해당 답변은 +67의 투표를 얻었는데... 여러분은 어떤가요? ^^ 방법이 마음에 드시나요?

두 번째는, 호스트 측에서 컨테이너의 uid/gid에 해당하는 값으로 볼륨 디렉터리의 소유권을 변경하는 것입니다. 예를 들어, 위와 같은 환경에서는 컨테이너의 uid/gid가 모두 1654이므로, 호스트 측에서 다음과 같이 볼륨 권한을 바꿔야 합니다.

// docker 호스팅 서버에서 실행

$ sudo chown -R 1654:1654 /mnt/docker-data/docker/volumes/dotnet_test_dir2

$ sudo ls -la /mnt/docker-data/docker/volumes/dotnet_test_dir2
total 24
drwx-----x   3 1654 1654    27 Oct 11 13:54 .
drwxrwxr-x 239 root rnd  20480 Oct 11 13:54 ..
drwxr-xr-x   2 1654 1654    30 Oct 11 14:13 _data

그럼 저렇게 1654:1654로 소유권이 변경되었으므로 컨테이너 내부에서 쓰기가 가능해집니다.

마지막 세 번째 방법은, 그냥 아예 "chmod 777 ..."로 볼륨 디렉터리의 권한을 바꾸자는 건데요, 이건 -3개의 투표를 얻어맞았습니다. ^^




참고로, 컨테이너 내에서 설정한 "USER ..."의 사용자 명은 해당 컨테이너 이미지에 미리 정의된 값입니다. 예를 들어, 위에서 사용한 "mcr.microsoft.com/dotnet/runtime:8.0" 이미지의 경우 "app" 사용자는 다음과 같이 정의돼 있어,

app@119fdfa35e6d:/$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
...[생략]...
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
app:x:1654:1654::/home/app:/bin/sh

그래서 "1654"라는 uid/gid가 할당된 것입니다. 또는, "USER ..." 설정이 없는 Dockerfile 설정이라면 docker run 시에 --user 옵션을 사용해 USER 명령어를 흉내 낼 수 있습니다.

// USER 설정이 없다면 root 권한으로 실행됨

$ docker run --rm -it --name dotnet_test_inst dotnet_test /bin/bash
root@6c18a4075d7a:/# id
uid=0(root) gid=0(root) groups=0(root)

// --user 옵션으로 USER 설정을 대신

$ docker run --rm -it --user 1654:1654 --name dotnet_test_inst dotnet_test /bin/bash
app@4c2548122e80:/$ id
uid=1654(app) gid=1654(app) groups=1654(app)

그렇다면 이미 USER 설정이 있는 경우 --user 옵션을 주면 어떻게 될까요?

$ docker run --rm -it --user 0:0 --name dotnet_test_non_root_inst2 dotnet_test_non_root /bin/bash
root@339c5441731b:/# id
uid=0(root) gid=0(root) groups=0(root)

보는 바와 같이 USER 설정을 무시하고 --user 옵션이 우선 적용됩니다. 그리고 --user, -u 옵션은 docker exec 명령어에서도 그대로 적용할 수 있습니다.




마지막으로 위의 글을 실습했다면! ^^

docker rmi dotnet_test
docker rmi dotnet_test_non_root

docker volume rm dotnet_test_dir
docker volume rm dotnet_test_dir2




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 10/20/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...
NoWriterDateCnt.TitleFile(s)
13301정성태3/29/202318693Windows: 239. C/C++ - Windows 10 Version 1607부터 지원하는 /DEPENDENTLOADFLAG 옵션 [1]파일 다운로드1
13300정성태3/28/202318050Windows: 238. Win32 - Modal UI 창에 올바른 Owner(HWND)를 설정해야 하는 이유
13299정성태3/27/202317779Windows: 237. Win32 - 모든 메시지 루프를 탈출하는 WM_QUIT 메시지
13298정성태3/27/202316925Windows: 236. Win32 - MessageBeep 소리가 안 들린다면?
13297정성태3/26/202318699Windows: 235. Win32 - Code Modal과 UI Modal
13296정성태3/25/202318015Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지 [1]파일 다운로드1
13295정성태3/24/202318220Windows: 233. Win32 - modeless 대화창을 modal처럼 동작하게 만드는 방법파일 다운로드1
13294정성태3/22/202317884.NET Framework: 2105. LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째
13293정성태3/22/202317014오류 유형: 853. dumpbin - warning LNK4048: Invalid format file; ignored
13292정성태3/21/202317757Windows: 232. C/C++ - 일반 창에도 사용 가능한 IsDialogMessage파일 다운로드1
13291정성태3/20/202317796.NET Framework: 2104. C# Windows Forms - WndProc 재정의와 IMessageFilter 사용 시의 차이점
13290정성태3/19/202318969.NET Framework: 2103. C# - 윈도우에서 기본 제공하는 FindText 대화창 사용법파일 다운로드1
13289정성태3/18/202316822Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법파일 다운로드1
13288정성태3/17/202317415Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법파일 다운로드1
13287정성태3/16/202317999Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법파일 다운로드1
13286정성태3/15/202318501Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
13285정성태3/14/202317153Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법 [2]파일 다운로드1
13284정성태3/13/202316127Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법파일 다운로드1
13283정성태3/12/202314059오류 유형: 852. 파이썬 - TypeError: coercing to Unicode: need string or buffer, NoneType found
13282정성태3/12/202314799Linux: 58. WSL - nohup 옵션이 필요한 경우
13281정성태3/12/202315860Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/202318583개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/202318029오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/202318164개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/202318344개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/202318478.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어 [1]
... 16  17  18  19  20  21  22  23  24  25  26  27  28  29  [30]  ...