Docker permission denied 오류: 발생 원인과 올바른 해결 방법
요약
이 글은 'Docker permission denied' 오류가 단순히 Docker의 고장이 아니라, 사용자 권한 문제임을 설명합니다. 이 오류는 컨테이너 시작 전 데몬 통신 실패와 실행 중 파일 쓰기 실패 두 가지 경우로 구분해야 합니다. 특히 `sudo` 사용 후 발생하는 소유권 문제 해결 방법과 root 소유 바인드 마운트 시의 일반적인 파일 접근 문제를 다룹니다.
핵심 포인트
- 오류는 데몬 통신 오류 또는 컨테이너 내부 파일 쓰기 실패 중 하나임.
- `sudo docker ...` 실행 시 `~/.docker` 디렉터리가 root 소유가 되어 권한 문제가 발생할 수 있음.
- `chown` 명령을 사용하여 사용자 소유권으로 되돌리는 것이 중요함.
- 호스트 경로를 명시하지 않은 바인드 마운트는 root 소유로 생성되어 컨테이너 접근에 문제를 일으킬 수 있음.
Docker permission denied: 이 오류가 실제로 의미하는 것
저에게 매주 클라이언트들이 이 오류에 대해 문의합니다. 그들은 명령을 실행하고 "permission denied"를 보고, Docker가 고장 났다고 생각합니다.
Docker는 거의 고장 나지 않습니다. 사람들은 단지 두 가지 완전히 다른 오류에 하나의 이름만 사용합니다.
첫 번째는 컨테이너가 시작되기 전에 발생합니다: 사용자가 Docker 데몬(daemon)과 통신할 수 없는 경우입니다. 두 번째는 실행 중인 컨테이너 내부에서 발생합니다: 애플리케이션이 파일을 쓸 수 없는 경우입니다.
해결 방법들은 아무 관련이 없으므로, 먼저 이 둘을 구분해야 합니다. 아래에 제가 클라이언트 작업에서 본 네 가지 원인을 소개합니다.
"docker permission denied"가 한 문장으로 의미하는 것
Docker permission denied는 파일을 쓰려고 시도했거나 데몬과 통신하려 했던 프로세스가 파일(또는 소켓)의 소유자가 아닌 사용자이며, 그룹이나 다른 권한도 가지고 있지 않다는 것을 의미합니다.
커널은 www-data나 hitesh 같은 이름에는 신경 쓰지 않습니다. 대신 프로세스의 UID와 GID를 파일의 소유자 및 모드 비트와 비교합니다.
| 소켓 오류 (Socket error) | 파일 오류 (File error) | |
|---|---|---|
| 보이는 메시지 | permission denied ... docker.sock | 컨테이너 로그에서 Permission denied 또는 EACCES |
| ... |
그룹 멤버십은 로그인 시 읽히기 때문에, 오래된 셸(shell)에는 이 정보가 없습니다. newgrp docker는 현재 셸에만 문제를 해결합니다. 저는 한때 변화를 받지 못한 tmux pane 때문에 20분을 날린 적이 있습니다.
~/.docker/config.json의 함정
그룹 설정을 수정하기 전에 sudo docker ...를 실행했다면, 다음 경고 메시지를 볼 수 있습니다:
WARNING: Error loading config file: /home/hitesh/.docker/config.json - stat /home/hitesh/.docker/config.json: permission denied
sudo로 실행했기 때문에 ~/.docker가 root 소유가 되었고, 따라서 본인 사용자(user)는 이를 읽을 수 없습니다. 권한을 되돌려주세요:
sudo chown "$USER":"$USER" "$HOME/.docker" -R sudo chmod g+rwx "$HOME/.docker" -R
솔직한 경고
Docker 자체 문서에서도 docker 그룹이 root 수준의 권한을 부여한다는 점을 경고합니다. 이 그룹에 속한 누구든 docker run -v /:/host -it alpine chroot /host를 실행하여 호스트(host)에서 root 셸을 얻을 수 있습니다.
이는 제 단일 사용자 VPS에서는 괜찮습니다. 하지만 공유 서버(shared box)에서는 sudo의 안전한 대체재가 아닙니다.
원인 2: root 소유 바인드 마운트와 non-root 컨테이너
가장 흔한 파일 오류입니다. UID 1000으로 실행되는 컨테이너에 ./data를 마운트하는데, 호스트 디렉토리가 root 소유이고 앱이 EACCES: permission denied로 종료됩니다.
보통 Docker가 생성합니다. 만약 호스트 경로가 누락되고 짧은 볼륨 구문(short volume syntax)을 사용하면, Docker는 이를 root 소유로 생성합니다. 이 때문에 저는 첫 컨테이너 실수 목록에 이것을 넣어두었습니다.
세 가지 명령어가 불일치를 보여줍니다:
`id
uid=1000(hitesh) gid=1000(hitesh)
ls -ln /srv/app
drwxr-xr-x 2 0 0 4096 Oct 7 10:02 data
docker compose exec app id
uid=1000(node) gid=1000(node)
`
커널은 이름이 아닌 숫자를 비교하기 때문에 ls -l 대신 ls -ln을 사용해야 합니다. 여기서는 프로세스가 1000이고 디렉토리가 모드 755인 0으로 되어 있어 쓰기가 실패합니다.
숫자를 일치시키세요: 디렉토리 소유권을 컨테이너의 UID로 변경하거나, 호스트 사용자(host user)로 컨테이너를 실행하세요:
`services:
app:
image: myapp:latest
user: "1000:1000"
volumes:
- ./data:/data`
linuxserver.io 같은 커뮤니티 이미지는 동일한 작업을 위해 PUID와 PGID 환경 변수를 사용합니다. 이는 Docker 기능이라기보다는 관례(convention)이므로, 이 설정을 읽는 이미지에서만 작동합니다.
environment:
- PUID=1000
- PGID=1000
순서가 중요합니다. 컨테이너를 처음 시작하기 전에 올바른 소유자로 디렉토리를 생성해야 합니다. 그렇지 않으면 이미지가 절반만 초기화된 상태에서 루트(root) 소유 폴더를 정리하게 됩니다:
mkdir -p /srv/app/data sudo chown 1000:1000 /srv/app/data docker compose up -d
rootless 모드 또는 userns-remap 사용 시
두 모드 모두 UID를 변경합니다. rootless 모드에서는 컨테이너의 UID 0이 데몬을 실행하는 호스트 사용자에게 매핑되므로, 본인의 파일이 내부에서 루트 소유로 보이는 것입니다. 컨테이너의 UID n (1 이상)은 subuid + (n - 1)에 매핑됩니다. /etc/subuid에 hitesh:100000:65536가 있는 경우, UID 1000은 100999가 됩니다.
userns-remap을 사용하면 컨테이너의 UID 0이 첫 번째 하위(subordinate) UID에 매핑되고, UID n은 subuid + n에 매핑됩니다. 따라서 동일한 100000 시작 값일 경우, UID 1000은 101000이 됩니다. GID도 /etc/subgid를 따라 같은 방식으로 작동합니다. 매핑된 숫자로 chown을 수행해야 합니다.
원인 3: 앱이 실행 중에 소유하지 않은 디렉토리에 파일을 쓰는 경우
이 경우는 시작 시 모든 검사를 통과하지만 나중에 실패하는 경우입니다. 제가 가장 명확하게 경험한 사례는 Docker에서 FreePBX 17을 사용했을 때였습니다. 모듈 설치 배치가 끝난 후, 관리자 패널은 깨진 JavaScript와 함께 로드되었고 로그에는 다음과 같이 표시되었습니다:
file_put_contents(/var/www/html/admin/assets/js/pbxlib_...js): Failed to open stream: Permission denied
설치는 docker exec을 통해 root로 실행되었기 때문에, 생성된 파일들은 root 소유였습니다. 해당 이미지의 Apache는 asterisk 사용자로 실행되므로, 번들(bundle)을 재생성하려고 할 때 /var/www/html/admin/assets/js가 쓰기가 불가능했습니다.
누가 디렉토리를 소유하고 있고, Apache는 누구인가?
docker compose exec freepbx ls -ld /var/www/html/admin/assets/js
docker compose exec freepbx ps -eo user,comm | grep apache2
FreePBX 방식으로 소유권 수정
docker compose exec freepbx fwconsole chown
docker compose exec freepbx fwconsole reload
fwconsole chown을 사용하자 트리가 asterisk:asterisk 소유권과 모드 775로 설정되었고 오류가 사라졌습니다.
이 수정 방법은 해당 이미지에서 Apache가 www-data가 아닌 asterisk로 실행되기 때문에만 올바릅니다. 만약 Apache가 www-data로 실행되는 이미지라면, 동일한 chown 명령어가 웹 서버를 차단하게 됩니다.
그래서 저는 항상 앱의 사용자 계정으로 설치 및 업그레이드를 수행하거나, 그 직후에 해당 앱 자체의 소유권 도구를 실행하는 규칙을 세웠습니다.
원인 4: Fedora, RHEL 및 CentOS 호스트의 SELinux
때로는 소유자(owner)와 모드(mode)가 완벽해도 쓰기 작업이 실패할 수 있습니다. Fedora, RHEL, CentOS, Rocky 또는 Alma에서 이 경우 SELinux를 확인해야 합니다.
getenforce
Enforcing
ls -ld /srv/app/data
drwxr-xr-x. 2 1000 1000 6 Oct 7 11:40 /srv/app/data
ls -ldZ /srv/app/data
drwxr-xr-x. 2 1000 1000 unconfined_u:object_r:var_t:s0 6 Oct 7 11:40 /srv/app/data
sudo ausearch -m avc -ts recent
avc: denied { write } for comm=
-
이 방법은 원인을 숨깁니다. 소유자 문제는 여전히 잘못되었고, 앱이 어떤 사용자로 실행되는지 절대 알 수 없습니다.
-
이는 앱의 보안 가정을 깨뜨립니다. Nextcloud는 데이터베이스 비밀번호를 설정 파일에 보관하며, 777 권한은 호스트의 모든 사용자가 이를 읽고 편집할 수 있게 합니다.
-
지속되지 않습니다. 다음 컨테이너나 업그레이드가 자체 소유자와 모드로 새 파일을 작성하면 문제가 재발합니다.
제가 확인하는 순서
-
기억에 의존하지 말고 전체 오류 메시지를 읽으세요.
-
소켓인지 파일인지 결정하세요.
docker.sock이 언급되면 이것은 원인 1입니다. -
ls -ln으로 호스트에서 경로의 소유자를 확인하세요. -
docker compose exec app id로 내부 프로세스가 어떤 사용자로 실행되는지 확인하세요. 숫자가 다르면 원인 2 또는 3입니다. -
숫자가 일치하고 호스트가 SELinux를 강제하는 경우,
ausearch -m avc를 실행하세요. 이것이 원인 4입니다.
만약 오류가
이 글은 AI의 도움을 받아 작성되었고 사람이 검토했습니다. FreePBX 사례는 임시 VM(throwaway VM)에서 실험실 환경으로 재현되었으며, 표시된 다른 명령어 출력 결과는 중요한 줄만 잘라낸 Debian 12 머신의 일반적인 출력입니다.
만약 직접 처리하고 싶으시다면: 저는 생계형 자체 호스팅 스택을 고칩니다에서 서비스를 제공하며, 권한 문제는 보통 30분 정도 걸리는 작업입니다.
이 글은 AI(저희 자체 출판 자동화)의 도움을 받아 작성되었고 게시 전에 사람이 검토했습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기