Fuwari Banner
지니제스트Tech Archive
NAS & 홈서버7분 소요

시놀로지 NAS Docker 권한 에러와 PUID PGID 매핑 해결

ggeniezst

시놀로지 NAS 환경에서 Docker 컨테이너 구동 시 발생하는 EACCES 및 Permission denied 파일 권한 오류 원인을 분석하고 PUID와 PGID 및 ACL 매핑으로 해결하는 방법을 다룹니다.

Sponsored

시놀로지 NAS(Synology DSM)의 컨테이너 매니저(Container Manager)나 Docker Compose 환경에서 애플리케이션을 구동하다 보면, 볼륨 마운트 디렉터리에 파일을 생성하지 못하고 Permission denied 또는 EACCES: permission denied 에러를 출력하며 컨테이너가 무한 재시작(CrashLoop)에 빠지는 현상이 빈번하게 발생합니다. 또한 컨테이너가 정상적으로 동작하더라도, 내부 프로세스가 생성한 로그나 다운로드 파일을 윈도우 탐색기(SMB)나 macOS Finder에서 수정하거나 삭제하려 할 때 접근 거부 경고창이 뜨며 파일 관리가 불가능해지는 문제가 함께 뒤따릅니다.

이 문제는 리눅스 커널의 기본 파일 권한(POSIX DAC)과 시놀로지 DSM 특유의 윈도우 호환 액세스 제어 목록(ACL) 체계, 그리고 도커 컨테이너가 실행되는 유저 네임스페이스 간의 UID/GID 불일치에서 비롯됩니다.


에러 증상과 볼륨 접근 거부 로그 분석

호스트 NAS의 공유 폴더를 컨테이너 내부 경로로 바인드 마운트(volumes:)한 뒤 컨테이너를 구동하면, 서비스 프로세스가 디렉터리 생성이나 초기 설정 파일 쓰기를 시도하다가 운영체제 레벨에서 거부당합니다.

Node.js 기반 서비스나 웹 애플리케이션에서는 다음과 같은 파일 시스템 예외 로그가 기록됩니다:

TEXT
Error: EACCES: permission denied, mkdir '/config/logs'
    at Object.mkdirSync (node:fs:1398:3)
    at initLogging (/app/server.js:42:8)
[emerg] 1#1: mkdir() "/var/cache/nginx/client_temp" failed (13: Permission denied)

데이터베이스 컨테이너(PostgreSQL 등)의 경우 데이터 디렉터리의 소유권이 엄격하게 검증되므로 초기화 단계에서 즉시 프로세스가 종료됩니다:

TEXT
FATAL: data directory "/var/lib/postgresql/data" has wrong ownership
HINT: The server must be started by the user that owns the data directory.

컨테이너 장애 상태를 진단하기 위해 SSH 터미널에서 컨테이너 실행 로그와 마운트 대상 디렉터리의 실제 호스트 권한을 확인합니다:

BASH
# 장애가 발생한 컨테이너 로그 확인
docker logs --tail 50 my-app

# 호스트 파일 시스템의 디렉터리 권한 및 UID/GID 숫자 조회
ls -ldn /volume1/docker/my-app/config

조회 결과를 살펴보면 호스트 디렉터리는 시놀로지 관리자 계정(예: UID 1026, GID 100)으로 생성되어 있는 반면, 컨테이너 내부 프로세스는 기본 node 계정(UID 1000)이나 postgres 계정(UID 999), 혹은 격리된 유저로 기동되면서 권한 충돌이 발생합니다. 호스트 파일 시스템의 기타 사용자(Others) 쓰기 권한이 닫혀 있으므로 커널의 가상 파일 시스템(VFS) 계층에서 쓰기 호출이 즉시 차단되는 구조입니다.


시놀로지 ACL과 리눅스 컨테이너 권한 메커니즘

문제가 발생하는 근본적인 원인을 이해하려면 리눅스 커널의 프로세스 자격 증명(Credentials) 검증과 시놀로지 DSM 파일 시스템의 고유한 권한 모델을 파악해야 합니다.

Docker 컨테이너는 가상 머신(VM)과 달리 별도의 독립된 OS 커널을 실행하지 않고, 호스트 NAS의 리눅스 커널을 공유합니다. 기본적으로 도커 데몬에 사용자 네임스페이스 격리(userns-remap)가 설정되어 있지 않다면, 컨테이너 내부에서 실행되는 프로세스의 UID와 GID는 호스트 커널의 VFS에서 동일한 숫자로 평가됩니다.

시놀로지 DSM은 일반적인 리눅스 배포판과 계정 생성 규칙이 다릅니다. 일반 데비안이나 우분투는 첫 번째 일반 사용자에게 UID 1000을 부여하지만, 시놀로지 DSM은 시스템 예약 계정 대역을 피해 초기 설정 시 등록한 관리자 계정에게 보통 UID 1026(또는 1024 이상)을 할당하며 기본 소속 그룹은 users(GID 100)로 지정됩니다.

계정 및 환경 구분 기본 UID 기본 GID 파일 소유권 및 접근 권한 특징
컨테이너 기본 root 0 0 호스트 파일 생성 시 일반 계정 수정 불가
일반 배포판 기본 유저 1000 1000 시놀로지 미등록 계정으로 Others 권한 적용
시놀로지 관리자 계정 1026 100 File Station 및 SMB 네트워크 드라이브 소유자

여기에 더해 시놀로지는 Btrfs 및 ext4 파일 시스템 위에 윈도우 네트워크 공유(SMB)와 완벽히 호환되는 자체적인 접근 제어 목록(Synology Windows ACL)을 얹어서 운용합니다. 리눅스 표준 POSIX 권한(rwxrwxrwx) 외에도 상속 플래그(is_inherit)와 확장 속성이 결합되어 있습니다.

터미널에서 단순히 chmod -R 777을 실행할 경우 일시적으로 쓰기가 가능해질 수 있으나, 시놀로지의 ACL 상속 비트가 손상되어 파일 스테이션(File Station)에서 권한 경고가 발생하거나 새 파일이 생성될 때 다시 권한이 잠기는 부작용이 생깁니다. 따라서 컨테이너 내부 실행 프로세스의 UID/GID를 시놀로지 호스트 사용자 계정의 UID/GID와 일치시키는 것이 근본적인 해결책입니다.


PUID PGID 환경변수와 Docker 네이티브 user 설정

컨테이너와 호스트 계정을 일치시키는 방법은 사용하는 컨테이너 이미지의 내부 구동 아키텍처에 따라 크게 두 가지로 나뉩니다.

시놀로지 계정의 UID와 GID 확인

먼저 컨테이너가 사용할 시놀로지 DSM 사용자 계정의 식별 번호를 SSH 터미널에서 확인합니다:

BASH
# 현재 로그인한 시놀로지 계정의 UID 및 GID 확인
id

# 특정 계정(예: adminuser)의 정확한 식별 번호 조회
synouser --get adminuser

출력 결과에서 uid=1026(adminuser) gid=100(users)와 같이 UID와 GID 값을 확인하여 메모합니다.

LinuxServer 계열 이미지의 PUID PGID 구성

LinuxServer.io에서 배포하는 이미지(lscr.io/linuxserver/...)는 내부에 s6-overlay 프로세스 관리자가 내장되어 있습니다. 컨테이너가 루트 권한으로 기동한 직후 환경변수로 전달된 PUIDPGID 값을 읽어 내부 작업 계정(abc)의 식별자를 호스트와 동일하게 동적으로 변경한 뒤 권한을 강등(Drop Privilege)시켜 메인 애플리케이션을 구동합니다.

YAML
version: "3.8"

services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    environment:
      - PUID=1026
      - PGID=100
      - TZ=Asia/Seoul
      - UMASK=022
    volumes:
      - /volume1/docker/qbittorrent/config:/config
      - /volume1/downloads:/downloads
    ports:
      - "8080:8080"

여기서 UMASK=022를 함께 지정하면 새로 생성되는 파일이 644, 디렉터리가 755 권한으로 생성되므로 SMB 네트워크 드라이브를 통해 다른 사용자가 파일을 읽는 데 지장이 발생하지 않습니다.

공식 도커 허브 이미지의 네이티브 user 지시어 구성

반면 Node, Python, Nginx 등 Docker 공식 이미지(Official Images)는 s6-overlay를 포함하지 않으므로 PUIDPGID 환경변수를 전달해도 무시됩니다. 이러한 이미지에는 도커 네이티브 지시어인 user:를 직접 정의해야 합니다:

YAML
version: "3.8"

services:
  node-app:
    image: node:20-alpine
    container_name: node-service
    restart: unless-stopped
    user: "1026:100"
    working_dir: /app
    command: ["node", "index.js"]
    volumes:
      - /volume1/docker/node-app:/app
    ports:
      - "3000:3000"

user: "1026:100" 설정을 주입하면 도커 데몬이 컨테이너의 메인 프로세스를 시작할 때 커널 레벨에서 프로세스 자격 증명을 UID 1026, GID 100으로 설정합니다. 따라서 볼륨 디렉터리에 파일을 쓰더라도 호스트 관리자 계정 명의로 생성되어 어떠한 권한 충돌도 일어나지 않습니다.


시놀로지 파일 스테이션 권한 상속과 검증 절차

설정 파일을 적용한 후에는 호스트 파일 시스템의 권한을 정돈하고 실제 정상 동작 여부를 단계별로 검증해야 합니다.

호스트 디렉터리 권한 정돈 및 ACL 동기화

기존에 root 계정으로 컨테이너를 구동하여 파일 소유권이 꼬여 있다면, SSH에서 대상 디렉터리의 소유권을 시놀로지 계정으로 재설정합니다:

BASH
# 대상 볼륨 폴더 소유권을 시놀로지 계정(1026:100)으로 일괄 복구
chown -R 1026:100 /volume1/docker/node-app

# 시놀로지 전용 ACL 설정 상태 진단
synoacltool -get /volume1/docker/node-app

DSM 웹 콘솔의 파일 스테이션(File Station)을 활용하는 경우, 해당 폴더 우클릭 후 속성 -> 권한 탭으로 이동합니다. 관리자 계정에 읽기 및 쓰기 권한이 부여되어 있는지 확인하고, 하단의 이 폴더, 하위 폴더 및 파일에 적용 체크박스를 활성화한 뒤 저장하여 전체 하위 트리에 시놀로지 ACL을 전파합니다.

컨테이너 기동 및 파일 생성 검증

디렉터리 권한 정돈이 완료되면 Docker Compose 서비스를 재기동하고 테스트 파일을 생성하여 권한이 정상 유지되는지 확인합니다:

BASH
# Compose 서비스 백그라운드 재기동
docker compose up -d --force-recreate

# 컨테이너 내부 셸을 통해 테스트 파일 쓰기 수행
docker exec node-service touch /app/perm_test.txt

# 호스트 파일 시스템에서 실제 생성된 파일의 소유자 확인
ls -l /volume1/docker/node-app/perm_test.txt

출력 결과 파일 소유자가 adminuser users 또는 숫자 1026 100으로 올바르게 표기된다면 컨테이너와 호스트 간의 볼륨 매핑이 완전히 동기화된 것입니다. 마지막으로 테스트 파일을 컨테이너 내부에서 삭제하여 삭제 권한까지 확인합니다:

BASH
# 테스트 파일 삭제를 통한 수정 및 삭제 권한 종합 확인
docker exec node-service rm /app/perm_test.txt

실무 환경 사이드 이펙트 방지 FAQ

NAS 환경에서 컨테이너 계정 권한을 조정할 때 엔지니어들이 현업에서 자주 겪는 예외 상황과 대처법을 정리했습니다.

user 지시어 적용 후 1024 이하 특권 포트 바인딩 실패

user: "1026:100"과 같이 비특권 사용자로 컨테이너를 실행하면, 웹 서버(Nginx, Apache 등)가 80번이나 443번 같은 1024 이하의 특권 포트(Privileged Port)에 바인딩하려 할 때 bind() to 0.0.0.0:80 failed (13: Permission denied) 에러가 발생합니다.

리눅스 커널 보안 정책상 비루트 프로세스는 1024 미만 포트를 열 수 없습니다. 이 경우 컨테이너 내부 포트를 8080이나 8443과 같은 비특권 포트로 리슨하도록 서버 설정을 변경하고, 도커 포트 포워딩(ports: - "80:8080")을 이용해 호스트 80번과 연결해야 합니다. 또는 커널 기능(Capability)을 명시적으로 부여하는 방법도 있습니다:

YAML
# 비루트 계정에 포트 바인딩 커널 권한 추가
cap_add:
  - NET_BIND_SERVICE

PostgreSQL 등 공식 데이터베이스 이미지에서 user 설정 시 초기화 오류

공식 PostgreSQL 및 MySQL 이미지는 컨테이너 진입점(entrypoint) 스크립트가 초기에 root로 기동하여 데이터 디렉터리 권한을 스스로 검사하고, 내부 전용 유저(postgres UID 999 등)로 권한을 강등하여 데이터베이스를 초기화(initdb)합니다.

여기에 임의로 user: "1026:100"을 강제 지정하면 entrypoint 스크립트 내부의 chowngosu 명령어 실행이 실패하면서 setgroups: Operation not permitted 에러와 함께 컨테이너가 뻗어버립니다. 데이터베이스 이미지는 user: 지시어를 사용하지 말고 기본 상태로 두되, 호스트의 데이터 마운트 디렉터리 소유권을 미리 해당 DB의 내부 UID(PostgreSQL의 경우 UID 999)로 변경해 두거나 볼륨 디렉터리를 비워둔 채 초기 구동을 맡겨야 합니다.

Docker 소켓 마운트 시 권한 거부(Permission Denied) 문제

Watchtower, Portainer, Nginx Proxy Manager 등 도커 환경을 제어하는 관리형 컨테이너는 호스트의 도커 소켓(/var/run/docker.sock)을 마운트해야 합니다. 시놀로지 DSM 7 환경에서는 도커 소켓이 root:administrators 소유(보통 GID 101)에 660 퍼미션으로 엄격히 잠겨 있습니다.

비루트 계정으로 실행되는 관리 도구에 도커 소켓을 마운트하면 dial unix /var/run/docker.sock: connect: permission denied 오류가 발생합니다. 이 경우 컨테이너 프로세스가 호스트의 도커 데몬 소켓에 접근할 수 있도록 소켓 소유 그룹인 시놀로지 administrators 그룹 GID(보통 101)를 보조 그룹으로 추가해야 합니다:

YAML
# docker.sock 통신을 위한 administrators 그룹 ID 매핑
group_add:
  - "101"

공유 볼륨 환경에서 복수 컨테이너 간 파일 쓰기 충돌 방지

다운로더(qBittorrent)와 미디어 서버(Plex, Jellyfin)가 동일한 미디어 공유 폴더(/volume1/media)를 동시에 바라보는 구조에서는 컨테이너 간 쓰기 권한 충돌이 자주 발생합니다. 한 컨테이너가 다운로드한 파일을 다른 컨테이너가 읽거나 이동시키지 못하는 현상입니다.

이 문제는 양쪽 컨테이너의 그룹 ID를 모두 시놀로지 기본 그룹인 PGID=100(users)으로 통일하고, 다운로더 컨테이너의 UMASK002로 설정하여 해결합니다. 002 umask가 적용되면 새로 생성되는 파일의 그룹 권한에 쓰기 비트(664, 디렉터리 775)가 활성화되므로 동일한 users 그룹에 속한 다른 컨테이너 및 SMB 접속자가 파일을 자유롭게 변경하거나 이동할 수 있습니다. 컨테이너 간의 독립적인 가상 네트워크 분리와 통신 격리는 시놀로지 NAS Docker Compose 네트워크 격리 구성을 함께 적용하면 더욱 견고하고 안전한 홈랩 환경을 구축할 수 있습니다.

Sponsored