SSD Nodes Learn 🎉 VPS $5.50/월부터
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-13

Docker Compose PUID PGID 설정 이유와 올바른 사용법

PUID와 PGID는 Docker 설정이 아닌 linuxserver.io 이미지의 관례입니다. 바인드 마운트 파일이 911:911 권한으로 생성되는 원인을 분석하고, id -u 및 id -g 명령어로 호스트의 정확한 사용자 ID를 확인하여 권한 문제를 해결하는 방법을 설명합니다.

PUID와 PGID의 실제 의미

PUID와 PGID는 특정 컨테이너 이미지가 시작 시점에 읽어 들이는 두 개의 환경 변수입니다. Docker 자체는 이 변수들을 확인하지 않습니다. 이는 linuxserver.io 이미지와 그 외 일부 이미지에서 사용하는 관례일 뿐이며, 이를 읽도록 작성되지 않은 이미지는 해당 변수를 무시합니다.

linuxserver.io 이미지 내부에는 빌드 시점에 UID(사용자 ID) 911과 GID(그룹 ID) 911로 생성된 abc라는 사용자가 존재합니다. 컨테이너는 root 권한으로 시작하여 초기화 스크립트를 실행하며, 그중 하나의 스크립트가 다른 작업이 수행되기 전에 해당 사용자의 ID를 재설정합니다.

groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abc

-o 플래그는 이미 다른 곳에서 사용 중인 ID를 허용합니다. 그 후 초기화 과정에서 권한을 낮추고 애플리케이션을 abc 사용자로 실행합니다. 따라서 PUID=1000은 Docker에 전달되지 않습니다. 이 변수는 애플리케이션이 시작되기 전에 컨테이너 내부의 사용자 ID를 변경하므로, 애플리케이션이 작성하는 모든 파일은 호스트 디스크에서 1000번 사용자의 소유가 됩니다. PUID를 설정하지 않으면 abc는 911로 유지되며, 이것이 설정되지 않은 바인드 마운트(bind mount)에 911:911 소유의 파일이 쌓이는 이유입니다.

id를 사용하여 두 개의 숫자 확인하기

데이터 디렉터리를 소유한 사용자의 권한으로 호스트에서 다음 명령을 실행합니다.

id
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)

uid는 PUID이며 gid은 PGID입니다. 스크립트의 경우 id -uid -g를 사용하면 숫자만 출력됩니다. 대부분의 신규 VPS 이미지에서 첫 번째 일반 사용자 계정은 1000:1000이지만, 이를 당연하게 가정해서는 안 됩니다. 서버를 재구축하거나 나중에 추가한 계정은 1001 이상의 값을 가지며, 여기서 잘못된 숫자를 입력하는 것이 전체 오류의 원인이 됩니다. 서비스가 로그인 계정이 아닌 전용 서비스 계정으로 실행되는 경우, id thatuser을 실행하여 해당 계정의 숫자를 확인하십시오.

파일 소유자가 911:911로 표시되는 이유

ls -l은 해당 ID와 일치하는 호스트 계정이 없을 때 이름 대신 숫자 ID를 출력합니다. 서버에 UID 911인 계정이 없으므로 출력할 이름이 없는 것입니다. 항상 숫자를 확인하여 모호함을 없애려면 ls -ln을 사용하십시오.

ls -ln /srv/appdata/sonarr
drwxr-xr-x 2 911 911 4096 Aug  7 09:12 Backups
-rw-r--r-- 1 911 911  512 Aug  7 09:12 config.xml

위 출력 결과는 컨테이너가 내장된 기본값으로 실행되었음을 나타냅니다. 추측하지 말고 컨테이너 내부에서 직접 확인하십시오.

docker exec sonarr id abc
docker compose logs sonarr | head -n 25

linuxserver init은 시작 로그에 다음과 같이 두 줄로 결과를 출력합니다.

User UID:    911
User GID:    911

Compose 파일에서 PUID=1000를 설정했음에도 해당 줄에 911이 표시된다면, 변수가 컨테이너에 전달되지 않은 것입니다. 일반적으로 docker-compose.yml을 수정한 뒤 docker compose restart을 실행하면 기존 컨테이너의 환경 변수가 그대로 재사용되기 때문에 발생합니다. 환경 변수를 변경하려면 컨테이너를 다시 생성하는 docker compose up -d를 실행해야 합니다.

컨테이너가 생성한 파일을 삭제할 수 없는 이유

커널은 이름이 아닌 숫자를 비교합니다. 사용자의 셸은 UID 1000으로 실행되지만, 해당 파일은 UID 911의 소유입니다. 파일을 포함하는 디렉터리는 drwxr-xr-x이며 이 역시 911의 소유이므로, 그룹 및 기타 사용자는 읽기와 실행 권한만 가질 뿐 쓰기 권한은 없습니다. 파일을 삭제하려면 파일 자체가 아니라 해당 파일이 위치한 디렉터리에 대한 쓰기 권한이 필요합니다. 따라서 파일 자체는 문제가 없어 보여도 다음과 같은 오류가 발생합니다.

rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission denied

쓰기 작업을 수행하는 컨테이너도 반대편에서 동일한 문제에 직면합니다. 호스트 디렉터리가 755 모드로 사용자 소유이고 애플리케이션이 911로 실행되는 경우, 첫 번째 쓰기 시도부터 Permission denied 오류가 발생하며 애플리케이션은 이를 자체적인 메시지로 보고합니다. Sonarr나 Radarr 같은 .NET 애플리케이션에서는 이를 UnauthorizedAccessException: Access to the path '/data/downloads' is denied로 표시합니다. 파일 앞에 표시된 권한 문자열은 현재 어떤 권한 세트의 적용을 받고 있는지 알려주며, drwxr-xr-x를 올바르게 읽는 법을 익히면 이러한 오류를 명확하게 파악할 수 있습니다.

이는 특히 bind mount에서 발생하는 문제입니다. Docker가 빈 named volume을 생성하여 이미지 내에 존재하는 경로에 마운트할 때는, 해당 경로의 내용물과 소유권 및 권한 비트를 볼륨으로 복사하므로 애플리케이션은 이미 자신이 소유한 디렉터리를 찾게 됩니다. 반면 bind mount는 이러한 처리가 전혀 이루어지지 않습니다. Docker는 호스트 디렉터리를 있는 그대로 마운트하기 때문입니다. 이러한 차이는 bind mount가 named volume보다 유리한 경우와 그렇지 않은 경우를 알아야 하는 실질적인 이유 중 하나입니다.

이미 잘못된 디렉터리 수정하기

PUID와 PGID를 설정하면 애플리케이션의 향후 동작 방식이 변경됩니다. 하지만 이미 디스크에 존재하는 파일의 소유권까지 소급하여 수정하지는 않습니다. 스택을 중지하고 직접 소유권을 수정한 뒤 다시 시작하십시오.

docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -d

숫자를 직접 입력하기 번거롭다면 sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr을 사용하십시오. 컨테이너가 실행 중인 상태에서 재귀적으로 chown을 수행하면 디렉터리 트리가 절반만 수정되거나 혼란스러운 오류가 추가로 발생할 수 있으므로, 반드시 컨테이너를 중지한 상태에서 수행해야 합니다.

PUID와 PGID로 해결되지 않는 문제

모든 설정을 올바르게 마친 사용자들도 이 부분에서 자주 어려움을 겪습니다. linuxserver 초기화 스크립트는 시작 시 /app, /config, /defaults라는 정확히 세 개의 경로에 대해서만 chown을 수행합니다. 미디어 마운트 경로는 이 목록에 포함되지 않습니다. /data, /downloads, /tv은 수정되지 않은 상태로 애플리케이션에 전달됩니다. 따라서 해당 마운트의 호스트 측 소유권에 컨테이너 사용자가 쓰기 권한이 없다면, 컨테이너는 정상적으로 시작되어 배너에 올바른 UID를 출력하더라도 첫 번째 가져오기(import) 작업에서 실패하게 됩니다.

이는 올바른 동작 방식입니다. 컨테이너가 시작될 때마다 12TB 규모의 미디어 라이브러리 전체에 재귀적으로 chown를 수행하는 것은 재앙이 될 것입니다. 즉, 미디어 디렉터리의 권한 관리는 사용자의 몫이며, 실제로 권한 문제가 발생하는 곳도 바로 이 마운트 지점들입니다.

사용자 권한을 제어하는 세 가지 방법과 각 방법의 적용 시점

PUID 및 PGID 환경 변수

이 방법은 엔트리포인트에서 해당 변수를 읽어들이는 이미지에서만 작동합니다. 컨테이너가 root로 시작하여 자체 설정을 수행하고 /config를 수정한 뒤에야 권한을 낮추는 방식이라 널리 사용됩니다. Docker Mods와 사용자 정의 초기화 스크립트도 그대로 작동합니다. 단점은 플랫폼 기능이 아닌 관례에 의존하며, 변수명이 프로젝트마다 표준화되어 있지 않다는 점입니다.

Compose의 user:

이 방법은 실제 Docker 기능이며 모든 이미지에서 작동합니다. 컨테이너 런타임이 이미지 내부 코드가 실행되기 전에 이를 적용하기 때문입니다.

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    user: "1000:1000"

프로세스가 root로 실행되는 순간이 전혀 없으므로 보안상 확실한 이점이 있습니다. 반면 엔트리포인트에서 root 권한이 필요한 모든 작업은 실패하게 됩니다. linuxserver 이미지의 경우, 테스트를 거친 이미지에 한해 합리적인 수준에서 이 방식을 지원합니다. 주의할 점은 PUID와 PGID가 더 이상 효과가 없으며, Docker Mods와 사용자 정의 서비스가 실행되지 않고, 마운트된 모든 볼륨의 권한을 사용자가 직접 관리해야 한다는 것입니다. 해당 프로젝트의 문서화된 패턴은 이 플래그와 쓰기 가능한 /run을 함께 사용하는 것입니다.

user: 1000:1000
tmpfs:
  - /run:uid=1000,gid=1000,exec
security_opt:
  - no-new-privileges=true

사용자들이 당황하는 사소한 부작용이 하나 있습니다. 숫자 형태의 user:은 컨테이너의 /etc/passwd에 대응하는 항목이 없으므로, 내부 도구들은 whoami: cannot find name for user ID 1000이라고 보고합니다. ID 자체는 유효하며 파일 접근도 정상적으로 작동합니다. 단지 이름 조회만 실패할 뿐입니다.

Rootless Docker

Rootless Docker는 데몬 자체를 권한이 없는 일반 사용자 계정으로 실행하므로, 호스트 시스템의 그 어떤 것도 실제 root 권한으로 실행되지 않습니다. 이는 소유권 계산 방식을 완전히 바꿉니다. 컨테이너의 UID 0은 Rootless Docker를 실행하는 호스트 사용자의 UID로 매핑됩니다. 1 이상의 n에 대한 컨테이너 UID nsubuid + (n - 1)으로 매핑되는데, 여기서 subuid/etc/subuid/etc/subgid에 할당된 범위의 시작점입니다. Docker는 해당 파일들에 최소 65,536개의 하위 ID가 있을 것으로 예상합니다.

이 매핑 방식을 다시 확인하십시오. 일반적인 권장 사항과 정반대이기 때문입니다. Rootless Docker 환경에서 컨테이너가 root로 파일을 쓰면 호스트의 사용자 소유로 생성됩니다. 반면 UID 1000으로 파일을 쓰면 100999 근처의 하위 ID 소유로 생성되어, 사용자의 셸에서 직접 접근할 수 없게 됩니다. 따라서 rootful 데몬에서 올바르던 PUID 값이 여기서는 잘못된 값이 됩니다. 두 메커니즘은 서로 다른 계층에서 같은 문제를 해결하므로, 확인 없이 혼용하면 sudo 없이는 삭제할 수 없는 디렉터리가 생기게 됩니다. Rootless 환경으로 전환한다면, 라이브러리를 마이그레이션하기 전에 서버에서 파일 하나를 생성해 소유권을 먼저 테스트하십시오.

단일 VPS에서 운영하는 대부분의 셀프 호스팅 스택에는 rootful 데몬 환경에서 PUID와 PGID를 사용하는 것이 실용적인 선택입니다. 이미지가 이를 위해 빌드되고 문서화되어 있기 때문입니다. user:은 해당 이미지의 README에 테스트 완료 사실이 명시되어 있거나, PUID를 전혀 지원하지 않는 공식 업스트림 이미지를 사용할 때 선택하십시오. 단일 VPS의 셀프 호스팅 AFFiNE 인스턴스와 같은 문서 작업 공간이 바로 후자에 해당합니다. 이 경우 컨테이너 중 그 어떤 것도 PUID를 읽지 않으며, 데이터베이스 디렉터리와 업로드된 파일의 소유권은 환경 변수가 아닌 런타임에 의해 결정되기 때문입니다.

미디어 스택 사례: 컨테이너 간 그룹 공유

Sonarr, Radarr 및 다운로드 클라이언트를 포함한 arr 미디어 스택은 이론이 실제 적용되는 대표적인 사례입니다. 다운로드 클라이언트는 완료된 파일을 /data/downloads에 기록합니다. 이후 Sonarr는 해당 파일을 /data/media으로 하드링크하거나 이동합니다. 하드링크가 정상적으로 작동하려면 두 컨테이너 모두 동일한 디렉터리 트리에 대한 쓰기 권한이 있어야 합니다. 만약 다운로드 클라이언트는 1000으로, Sonarr는 1001로 실행된다면 한쪽은 파일을 소유하고 다른 쪽은 읽기만 가능한 상황이 발생합니다.

이 문제를 해결하려면 스택 내의 모든 컨테이너가 PGID로 사용할 공유 그룹을 설정해야 합니다.

sudo groupadd -g 13000 media
sudo usermod -aG media deploy
sudo chown -R deploy:media /srv/media
sudo find /srv/media -type d -exec chmod 2775 {} +
sudo find /srv/media -type f -exec chmod 0664 {} +

2775의 앞부분에 있는 2은 setgid 비트입니다. 디렉터리에 이 비트가 설정되면, 그 안에 생성되는 모든 새 파일과 하위 디렉터리는 생성자의 기본 그룹이 아닌 해당 디렉터리의 그룹인 media을 상속받습니다. 따라서 chown를 매번 다시 실행할 필요 없이 새로운 다운로드 파일에도 권한이 유지됩니다. 본인의 접근 권한을 확인하기 전에 로그아웃 후 다시 로그인하거나 newgrp media를 실행하십시오. usermod -aG으로 추가한 그룹은 이미 열려 있는 셸 세션에 즉시 반영되지 않습니다.

컨테이너 내부에서 groupmod -o -g 13000 abcabc 그룹의 번호를 13000으로 변경하므로, abc는 호스트의 media 그룹과 동일한 GID로 파일을 기록하게 됩니다. 스택 내의 모든 컨테이너는 각자의 PUID를 유지하면서 하나의 PGID를 공유합니다.

그다음 스택 내의 모든 linuxserver 컨테이너에 UMASK=002을 설정하십시오. 많은 사용자가 이 단계를 놓칩니다. 해당 이미지들의 기본값은 UMASK=022인데, 이는 모든 새 파일에서 그룹 쓰기 비트를 제거합니다. 결과적으로 파일 권한이 0644이 되어 방금 구성한 공유 설정이 무용지물이 됩니다. 002를 설정하면 파일은 0664, 디렉터리는 0775 권한으로 생성되어 그룹 쓰기가 가능해집니다.

services:
  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    environment:
      - PUID=${PUID}
      - PGID=${PGID}
      - UMASK=002
      - TZ=Etc/UTC
    volumes:
      - /srv/appdata/sonarr:/config
      - /srv/media:/data
    restart: unless-stopped

이 두 값은 Compose 파일 옆의 .env 파일에 정의하여 전체 스택이 하나의 설정을 참조하도록 합니다.

PUID=1000
PGID=13000

Compose는 ${PUID} 스타일 치환을 위해 해당 파일을 자동으로 읽어 들입니다. 이는 자격 증명을 관리할 때 사용하는 방식과 동일합니다. 값을 docker-compose.yml에서 분리하여 .env 파일로 관리하는 습관은 여기서도 동일하게 적용되지만, 이 두 숫자는 비밀값이 아니라는 점이 다릅니다.

설정을 맹신하지 말고 처음부터 끝까지 검증하십시오. 컨테이너 내부에서 파일을 생성한 뒤 호스트에서 읽어 보십시오.

docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtest

정상적인 결과라면 소유자는 본인의 PUID, 그룹은 13000, 모드는 -rw-rw-r--으로 표시되어야 합니다. 그룹 권한이 1000로 보인다면 해당 디렉터리에 setgid 비트가 누락된 것입니다. 모드가 -rw-r--r--로 보인다면 UMASK 변수가 적용되지 않은 것이므로, 컨테이너를 단순히 재시작하지 말고 새로 생성했는지 확인하십시오. 테스트가 끝나면 rm /srv/media/downloads/permtest으로 파일을 삭제하십시오.

어떤 이미지가 어떤 변수를 사용하는가

linuxserver.io 이미지는 PUID, PGIDUMASK을 사용합니다. Paperless-ngx는 동일한 개념에 대해 USERMAP_UIDUSERMAP_GID이라는 다른 이름을 사용하며, 두 변수 모두 기본값은 1000입니다. 해당 문서에서는 이 변수들을 id -uid -g에서 읽어오도록 안내합니다. 사진 서버들도 이와 같이 제각각입니다. PhotoPrism은 고유한 PHOTOPRISM_UIDPHOTOPRISM_GID 쌍을 가지고 있는 반면, Immich는 이에 대응하는 변수가 없으며 컨테이너 사용자를 Docker의 user: 키에 맡깁니다. 따라서 PhotoPrism과 Immich 중 선택하기는 서버 내 가장 큰 라이브러리를 관리하기 위해 어떤 메커니즘을 유지할지 결정하는 과정이기도 합니다. 일반적인 데이터베이스 및 웹 서버 이미지를 포함한 많은 공식 업스트림 이미지는 고정된 내장 사용자를 제공하며, 사용자가 user:를 사용하거나 그대로 두기를 기대합니다. 나중에 추가하는 인프라에도 동일하게 적용됩니다. 따라서 단일 로그인을 위해 애플리케이션 앞에 Authentik 배치하기를 수행할 때는 PUID를 전혀 읽지 않는 공식 서버, Postgres 및 Redis 이미지를 실행하게 되며, 볼륨 소유권은 구성 가능한 엔트리포인트가 아닌 런타임에 의해 결정됩니다.

그러므로 프로젝트 간에 환경 변수 블록을 복사하기 전에 각 이미지의 README를 확인하십시오. Docker는 내부에서 읽든 아니든 사용자가 설정한 모든 환경 변수를 컨테이너로 전달합니다. 아무것도 소비하지 않는 PUID는 오류나 경고를 발생시키지 않으며 아무런 효과도 없습니다. 컨테이너는 해당 Dockerfile의 마지막에 지정된 사용자로 실행되며, 생성된 파일의 소유권을 통해 이를 확인할 수 있습니다.

FAQ

왜 Docker 파일의 소유자가 911:911인가요?

911은 linuxserver.io 이미지에 내장된 abc 사용자의 UID 및 GID입니다. 이 숫자가 보인다는 것은 컨테이너가 PUIDPGID 설정 없이 시작되어, 초기화 스크립트가 내장된 기본값을 그대로 사용했음을 의미합니다. 호스트에 ID 911을 가진 계정이 없으면 표시할 이름이 없으므로 ls -l는 원시 숫자를 그대로 보여줍니다. id의 출력값을 PUIDPGID에 설정하고, docker compose up -d으로 컨테이너를 다시 생성한 뒤, 영향을 받는 디렉터리에 sudo chown -R 1000:1000를 실행하여 기존 파일의 소유권을 수정하십시오.

PUID와 PGID는 모든 Docker 이미지에서 작동하나요?

아닙니다. 이는 Docker의 기능이 아니며 Docker는 이를 읽지 않습니다. 이 설정은 이미지 자체의 진입점(entrypoint)이 이를 읽어 애플리케이션 시작 전 usermodgroupmod을 호출하는 경우에만 작동하며, linuxserver.io 제품군과 이 패턴을 차용한 일부 프로젝트가 이에 해당합니다. 다른 프로젝트는 paperless-ngx의 USERMAP_UIDUSERMAP_GID과 같이 다른 이름을 사용합니다. 이를 읽지 않는 이미지에서는 해당 변수를 설정해도 아무런 경고 없이 무시됩니다.

Docker Compose에서 PUID와 PGID를 사용해야 하나요, 아니면 user: 키를 사용해야 하나요?

이미지가 지원한다면 PUIDPGID을 사용하십시오. 진입점이 루트 권한으로 실행되면서 /config를 수정하고 서비스를 올바르게 시작할 수 있기 때문입니다. 이미지가 PUID를 지원하지 않거나 README에 비루트(non-root) 환경에서 테스트되었다고 명시된 경우에는 user:을 사용하십시오. linuxserver 이미지에서 user:를 설정하면 PUID와 PGID가 무력화되고, Docker Mods와 사용자 정의 서비스 실행이 중단되며, 모든 마운트된 볼륨의 권한 관리는 사용자의 책임이 됩니다.

Sonarr의 PUID가 올바른데도 파일을 이동할 수 없습니다. 무엇이 문제인가요?

다음 세 가지를 순서대로 확인하십시오. 첫째, 미디어 마운트 자체입니다. 초기화 과정은 /app, /config, /defaults만 chown하므로, /data이나 /downloads는 호스트의 소유권 상태를 그대로 유지합니다. 둘째, 공유 그룹입니다. 다운로드 클라이언트와 Sonarr가 서로 다른 GID로 실행되면 서로의 파일을 수정할 수 없으므로, 스택 내의 모든 컨테이너에 동일한 PGID을 부여하십시오. 셋째, umask입니다. 이미지 기본값인 UMASK=022은 그룹 쓰기 비트가 없는 0644로 파일을 작성하므로 공유 그룹 설정이 완전히 무의미해집니다. UMASK=002을 설정하고 chmod 2775를 사용하여 디렉터리에 setgid 비트를 설정하면 새 파일이 그룹을 상속받게 됩니다.