SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-09-23

PUID และ PGID ใน Docker Compose คืออะไรและตั้งค่าอย่างไร

PUID และ PGID ไม่ใช่การตั้งค่าของ Docker แต่เป็นข้อตกลงของ linuxserver.io เพื่อจัดการสิทธิ์ไฟล์บน Host เรียนรู้วิธีแก้ไขปัญหาไฟล์ติดสิทธิ์ 911:911 ให้เป็นผู้ใช้ปัจจุบัน

PUID และ PGID คืออะไร

PUID และ PGID คือตัวแปรสภาพแวดล้อม (environment variable) สองตัวที่ container image บางตัวจะอ่านค่าเมื่อเริ่มทำงาน ตัว Docker เองไม่ได้ตรวจสอบค่าเหล่านี้แต่อย่างใด ค่าเหล่านี้เป็นข้อตกลงร่วมกันที่ใช้ใน image ของ linuxserver.io และ image อื่นๆ อีกจำนวนหนึ่ง ดังนั้นหาก image ไม่ได้ถูกเขียนมาให้อ่านค่าเหล่านี้ มันก็จะเพิกเฉยต่อตัวแปรดังกล่าวโดยไม่มีการแจ้งเตือน

ภายใน image ของ linuxserver.io จะมีผู้ใช้ชื่อ abc ซึ่งถูกสร้างขึ้นในขั้นตอน build โดยมี UID (user ID) เป็น 911 และ GID (group ID) เป็น 911 ตัว container จะเริ่มทำงานในฐานะ root จากนั้นจะรันสคริปต์เริ่มต้น (init script) และหนึ่งในสคริปต์เหล่านั้นจะทำการเปลี่ยนหมายเลข ID ของผู้ใช้ดังกล่าวก่อนที่จะดำเนินการอื่นใด:

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

แฟล็ก -o อนุญาตให้ใช้ ID ที่มีการใช้งานอยู่แล้วในที่อื่นได้ หลังจากนั้นกระบวนการเริ่มต้นจะลดสิทธิ์ (drop privileges) และรันแอปพลิเคชันในฐานะ abc ดังนั้น PUID=1000 จึงไม่เคยถูกส่งไปถึง Docker ตัวแปรนี้จะทำการเปลี่ยนหมายเลขผู้ใช้ภายใน container ก่อนที่แอปพลิเคชันจะเริ่มทำงาน ซึ่งหมายความว่าทุกไฟล์ที่แอปพลิเคชันเขียนลงบนดิสก์ของคุณจะมีเจ้าของเป็น 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 -u และ id -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

ผลลัพธ์นั้นระบุว่า container ทำงานด้วยค่าเริ่มต้นที่มีมาในระบบ config.xml ในรายการดังกล่าวยังเป็นไฟล์ที่เก็บการตั้งค่าการยืนยันตัวตนด้วย ซึ่งมีความสำคัญเมื่อเปิด web UI ครั้งแรกแล้วพบว่า Sonarr และ Radarr ไม่มี username หรือ password เริ่มต้นมาให้ ตรวจสอบจากภายใน container แทนการคาดเดา:

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

กระบวนการเริ่มต้นของ linuxserver จะแสดงผลลัพธ์ใน log การเริ่มระบบเป็นสองบรรทัด:

User UID:    911
User GID:    911

หากบรรทัดเหล่านั้นแสดงค่า 911 หลังจากที่คุณตั้งค่า PUID=1000 ในไฟล์ Compose ของคุณ แสดงว่าตัวแปรนั้นไม่ได้ถูกส่งไปยังคอนเทนเนอร์ สาเหตุทั่วไปคือคุณแก้ไขไฟล์ docker-compose.yml แล้วรันคำสั่ง docker compose restart ซึ่งเป็นการนำคอนเทนเนอร์เดิมที่มีสภาพแวดล้อมเดิมกลับมาใช้ใหม่ การเปลี่ยนแปลงสภาพแวดล้อมจำเป็นต้องใช้คำสั่ง docker compose up -d ซึ่งจะสร้างคอนเทนเนอร์ขึ้นมาใหม่

เหตุผลที่คุณไม่สามารถลบไฟล์ที่คอนเทนเนอร์สร้างขึ้นได้

Kernel จะเปรียบเทียบตัวเลข ไม่ใช่ชื่อ Shell ของคุณทำงานในฐานะ UID 1000 แต่ไฟล์นั้นเป็นของ UID 911 ไดเรกทอรีที่เก็บไฟล์ดังกล่าวคือ drwxr-xr-x และเป็นของ 911 เช่นกัน ดังนั้นกลุ่ม (group) และผู้อื่น (other) จึงมีสิทธิ์อ่านและเรียกใช้งาน (read and execute) แต่ไม่มีสิทธิ์เขียน (write) การลบไฟล์ต้องใช้สิทธิ์เขียนบน ไดเรกทอรี ที่เก็บไฟล์นั้น ไม่ใช่ที่ตัวไฟล์เอง คุณจึงพบข้อผิดพลาดนี้แม้ว่าตัวไฟล์จะดูไม่มีอันตรายก็ตาม:

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

คอนเทนเนอร์ที่พยายามเขียนไฟล์จะเจอปัญหาเดียวกันจากอีกฝั่ง หากไดเรกทอรีบนโฮสต์เป็นของผู้ใช้ของคุณด้วยโหมด 755 และแอปพลิเคชันทำงานในฐานะ 911 การเขียนครั้งแรกจะล้มเหลวด้วย Permission denied และแอปจะรายงานข้อผิดพลาดในรูปแบบของตนเอง ในแอปพลิเคชัน .NET เช่น Sonarr หรือ Radarr ข้อผิดพลาดนี้จะแสดงเป็น UnauthorizedAccessException: Access to the path '/data/downloads' is denied สตริงสิทธิ์ที่อยู่หน้าชื่อไฟล์จะบอกคุณว่าคุณกำลังถูกตัดสินด้วยชุดสิทธิ์ใดในสามชุด และการ อ่าน drwxr-xr-x อย่างถูกต้อง คือสิ่งที่เปลี่ยนข้อผิดพลาดที่ดูซับซ้อนให้กลายเป็นเรื่องที่เข้าใจได้ทันที

นี่เป็นปัญหาที่เกิดจาก bind mount โดยเฉพาะ เมื่อ Docker สร้าง named volume ว่าง และ mount ทับ path ที่มีอยู่แล้วใน image มันจะคัดลอกเนื้อหาของ path นั้นลงใน volume รวมถึงความเป็นเจ้าของและบิตสิทธิ์ต่างๆ ทำให้แอปพลิเคชันพบไดเรกทอรีที่ตนเองเป็นเจ้าของอยู่แล้ว แต่ bind mount จะไม่ได้รับการจัดการในลักษณะนั้น Docker จะ mount ไดเรกทอรีบนโฮสต์ของคุณตามสภาพที่เป็นอยู่จริง ความแตกต่างนี้เป็นหนึ่งในเหตุผลเชิงปฏิบัติที่คุณควรทราบว่า เมื่อใดที่ bind mount ดีกว่า named volume และเมื่อใดที่ไม่ใช่

การแก้ไขไดเรกทอรีที่ตั้งค่าไว้ไม่ถูกต้อง

การตั้งค่า PUID และ PGID จะมีผลกับการทำงานของแอปพลิเคชันนับจากนี้เป็นต้นไป แต่จะไม่ย้อนกลับไปแก้ไขไฟล์ที่อยู่บนดิสก์อยู่ก่อนแล้ว ให้หยุดการทำงานของ stack แก้ไขความเป็นเจ้าของไฟล์ด้วยตนเอง จากนั้นจึงเริ่มการทำงานใหม่อีกครั้ง:

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 หากคุณไม่ต้องการพิมพ์ตัวเลขด้วยตนเอง ให้ดำเนินการในขณะที่ container หยุดทำงานอยู่เท่านั้น เนื่องจากหากแอปพลิเคชันที่กำลังทำงานอยู่มีการเขียนข้อมูลระหว่างที่ใช้คำสั่ง chown แบบ recursive อาจส่งผลให้โครงสร้างไดเรกทอรีถูกแก้ไขเพียงบางส่วน และนำไปสู่ข้อผิดพลาดรอบสองที่สร้างความสับสนได้

สิ่งที่ PUID และ PGID แก้ไขไม่ได้

นี่คือส่วนที่ทำให้ผู้ใช้งานที่ทำตามขั้นตอนอย่างถูกต้องต้องประสบปัญหา สคริปต์เริ่มต้นของ linuxserver จะทำการ chown ไฟล์เพียง 3 ตำแหน่งเท่านั้น คือ /app, /config และ /defaults โดยที่ mount point ของสื่อบันทึกข้อมูลของคุณไม่ได้อยู่ในรายการดังกล่าว เส้นทาง /data, /downloads และ /tv จะถูกส่งต่อไปยังแอปพลิเคชันโดยไม่มีการเปลี่ยนแปลงใดๆ ดังนั้นหากฝั่ง host ของ mount เหล่านี้มีสิทธิ์การเข้าถึงที่ผู้ใช้ใน container ไม่สามารถเขียนไฟล์ได้ container จะเริ่มทำงานได้ตามปกติ แสดง UID ที่ถูกต้องใน banner แต่จะล้มเหลวเมื่อเริ่มการนำเข้าข้อมูลครั้งแรก

นี่คือพฤติกรรมที่ถูกต้องแล้ว เพราะการทำ chown แบบ recursive บนคลังสื่อขนาด 12 เทราไบต์ทุกครั้งที่ container เริ่มทำงานจะเป็นหายนะอย่างยิ่ง ซึ่งหมายความว่าไดเรกทอรีสื่อบันทึกข้อมูลเป็นหน้าที่ของคุณในการจัดการ และเป็นจุดที่มักเกิดปัญหาเรื่องสิทธิ์การเข้าถึงจริง เนื่องจากความล้มเหลวประเภทนี้มักปรากฏเงียบๆ ใน log ของแอปพลิเคชันหลังจากที่ container ดูเหมือนทำงานปกติไปแล้วหลายชั่วโมง การทดสอบการเขียนไฟล์เป็นระยะโดยเชื่อมต่อเข้ากับ ntfy บน VPS ของคุณเอง เพื่อส่งการแจ้งเตือนไปยังโทรศัพท์ จึงเป็นวิธีที่ประหยัดในการรับทราบปัญหา ก่อนที่จะพบว่าตอนของซีรีส์หายไปเป็นสัปดาห์แล้วค่อยรู้ตัว

สามวิธีในการควบคุมผู้ใช้งาน และสถานการณ์ที่ควรใช้แต่ละวิธี

ตัวแปรสภาพแวดล้อม PUID และ PGID

วิธีนี้ใช้ได้เฉพาะกับอิมเมจที่ entrypoint อ่านค่าตัวแปรเหล่านี้เท่านั้น วิธีนี้เป็นที่นิยมเพราะคอนเทนเนอร์ยังคงเริ่มทำงานในฐานะ root เพื่อตั้งค่าต่างๆ แก้ไข /config แล้วจึงลดสิทธิ์ลงในภายหลัง Docker Mods และสคริปต์ init แบบกำหนดเองจึงยังคงทำงานได้ตามปกติ ข้อเสียคือคุณต้องเชื่อถือธรรมเนียมปฏิบัติแทนที่จะเป็นฟีเจอร์ของแพลตฟอร์ม และชื่อตัวแปรเหล่านี้ไม่ได้เป็นมาตรฐานเดียวกันในทุกโปรเจกต์

คีย์ user: ใน Compose

นี่คือฟีเจอร์ของ Docker โดยตรงและใช้งานได้กับทุกอิมเมจ เนื่องจาก container runtime จะนำไปใช้ก่อนที่โค้ดของอิมเมจจะเริ่มทำงาน:

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

กระบวนการทำงานจะไม่รันในฐานะ root เลยแม้แต่วินาทีเดียว ซึ่งถือเป็นการเพิ่มความปลอดภัยอย่างแท้จริง แต่ก็จะทำให้สิ่งที่อยู่ใน entrypoint ซึ่งจำเป็นต้องใช้สิทธิ์ root ทำงานไม่ได้ สำหรับอิมเมจของ linuxserver ทางโปรเจกต์รองรับวิธีนี้ตามความเหมาะสมและเฉพาะอิมเมจที่ผ่านการทดสอบแล้วเท่านั้น โดยมีข้อควรระวังเฉพาะคือ PUID และ PGID จะไม่มีผล Docker Mods จะไม่ทำงาน บริการแบบกำหนดเองจะไม่ทำงาน และคุณต้องรับผิดชอบสิทธิ์การเข้าถึงในทุก volume ที่ mount ไว้ รูปแบบที่เอกสารแนะนำคือการใช้แฟล็กนี้คู่กับ /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 จะรัน daemon ในฐานะผู้ใช้ที่ไม่มีสิทธิ์ของคุณเอง ดังนั้นจึงไม่มีสิ่งใดบนเซิร์ฟเวอร์ที่รันในฐานะ root จริงๆ วิธีนี้เปลี่ยนหลักการคำนวณความเป็นเจ้าของไฟล์ไปโดยสิ้นเชิง โดย UID 0 ของคอนเทนเนอร์จะแมปกับ UID ของโฮสต์ที่เป็นผู้รัน Rootless Docker และ UID n ของคอนเทนเนอร์สำหรับ n ใดๆ ที่มีค่าตั้งแต่ 1 ขึ้นไป จะแมปกับ subuid + (n - 1) โดยที่ subuid คือค่าเริ่มต้นของช่วงที่จัดสรรให้คุณใน /etc/subuid และ /etc/subgid ซึ่ง Docker คาดหวังว่าจะมี subordinate ID อย่างน้อย 65,536 รายการในนั้น

โปรดอ่านการแมปนี้อีกครั้ง เพราะมันกลับตรรกะจากคำแนะนำทั่วไป ภายใต้ Rootless Docker คอนเทนเนอร์ที่เขียนไฟล์ในฐานะ root จะสร้างไฟล์ที่เป็นของคุณ ส่วนคอนเทนเนอร์ที่เขียนไฟล์ในฐานะ UID 1000 จะสร้างไฟล์ที่เป็นของ subordinate ID แถวๆ 100999 ซึ่ง shell ของคุณไม่สามารถเข้าถึงได้ ดังนั้นค่า PUID ที่ถูกต้องบน rootful daemon จึงเป็นค่าที่ผิดในกรณีนี้ กลไกทั้งสองแก้ปัญหาเดียวกันในระดับที่ต่างกัน และการใช้ร่วมกันโดยไม่ตรวจสอบคือสาเหตุที่ทำให้ผู้ใช้ลงเอยด้วยไดเรกทอรีที่ต้องใช้ sudo ในการลบ หากคุณเลือกใช้ rootless ให้ทดสอบความเป็นเจ้าของไฟล์ที่เขียนขึ้นหนึ่งไฟล์บนเซิร์ฟเวอร์ของคุณก่อนที่จะย้าย library เข้าไป

สำหรับ stack ส่วนใหญ่ที่ self-host บน VPS เดียวกัน การใช้ PUID และ PGID บน rootful daemon เป็นทางเลือกที่ใช้งานได้จริงที่สุด เพราะเป็นสิ่งที่อิมเมจส่วนใหญ่ถูกสร้างและเขียนเอกสารกำกับไว้ ให้เลือกใช้ user: เมื่อ README ของอิมเมจระบุว่าผ่านการทดสอบสำหรับวิธีนี้แล้ว หรือเมื่อคุณรันอิมเมจทางการจากต้นทางที่ไม่มีการรองรับ PUID เลย พื้นที่ทำงานอย่าง อินสแตนซ์ AFFiNE ที่ self-host บน VPS เดียว จัดอยู่ในกรณีหลังนี้ เนื่องจากไม่มีคอนเทนเนอร์ใดอ่านค่า PUID และความเป็นเจ้าของไดเรกทอรีฐานข้อมูลรวมถึงไฟล์ที่อัปโหลดจะถูกกำหนดโดย runtime แทนที่จะเป็นสิ่งที่อยู่ใน environment block เช่นเดียวกับ ระบบสนับสนุน Chatwoot ที่ self-host ซึ่งคอนเทนเนอร์ Rails และ Sidekiq worker ต่างเขียนข้อมูลลงในไดเรกทอรี uploads เดียวกันและไม่มีตัวใดอ่านค่า PUID ดังนั้นไดเรกทอรีนั้นจึงต้องมีสิทธิ์ตรงกับผู้ใช้ที่อิมเมจรันอยู่แล้ว สิ่งเหล่านี้ไม่เปลี่ยนแปลงสำหรับ stack รุ่นใหม่ ดังนั้น การให้สมาชิกทุกคนในทีมมี OneCLI agent แบบแยกส่วน (sandboxed) จึงทำให้ไดเรกทอรีพื้นที่ทำงานของแต่ละคนและไดเรกทอรีข้อมูล Postgres เป็นของผู้ใช้ที่แต่ละอิมเมจรันอยู่แล้ว ซึ่งกลายเป็นปัญหาเรื่อง user: และ chown มากกว่าจะเป็นปัญหาเรื่อง PUID หากคุณ วาง API ที่ self-host ไว้หน้า Codex, Claude Code และ Hermes คุณก็จะได้รับโครงสร้างแบบเดียวกัน เพราะอิมเมจนั้นรันในฐานะผู้ใช้ที่ถูกกำหนดมาในตัว และ bind mount ที่เก็บฐานข้อมูลและคีย์ต่างๆ จะใช้สิทธิ์ความเป็นเจ้าของตามที่ผู้ใช้นั้นกำหนดมาให้

กรณีศึกษา Media Stack: การใช้กลุ่มผู้ใช้ร่วมกันระหว่างคอนเทนเนอร์

Arr media stack ซึ่งประกอบด้วย Sonarr, Radarr และโปรแกรมดาวน์โหลด เป็นตัวอย่างที่เห็นภาพชัดเจนที่สุด โปรแกรมดาวน์โหลดจะเขียนไฟล์ที่เสร็จสมบูรณ์ลงใน /data/downloads จากนั้น Sonarr จะทำการ hardlink หรือย้ายไฟล์นั้นไปยัง /data/media เพื่อให้การทำ hardlink สำเร็จ คอนเทนเนอร์ทั้งสองต้องมีสิทธิ์เขียนในโครงสร้างไฟล์เดียวกัน หากโปรแกรมดาวน์โหลดรันด้วย UID 1000 ในขณะที่ Sonarr รันด้วย 1001 ฝ่ายหนึ่งจะเป็นเจ้าของไฟล์ที่อีกฝ่ายอ่านได้อย่างเดียว

วิธีแก้ไขคือการสร้างกลุ่มผู้ใช้ร่วมที่ทุกคอนเทนเนอร์ใน stack ใช้เป็น 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 {} +

เครื่องหมาย 2 นำหน้าใน 2775 คือ setgid bit เมื่อตั้งค่าบนไดเรกทอรี ไฟล์และไดเรกทอรีย่อยใหม่ทุกรายการที่สร้างขึ้นภายในจะสืบทอดกลุ่ม media แทนที่จะเป็นกลุ่มหลักของผู้สร้าง ทำให้การตั้งค่านี้คงอยู่แม้มีการดาวน์โหลดไฟล์ใหม่โดยที่คุณไม่ต้องรัน chown ซ้ำ ให้ทำการออกจากระบบแล้วเข้าใหม่ หรือรัน newgrp media ก่อนตรวจสอบสิทธิ์ของคุณเอง เนื่องจากกลุ่มที่เพิ่มด้วย usermod -aG จะไม่ปรากฏใน shell session ที่เปิดค้างไว้

ภายในคอนเทนเนอร์ groupmod -o -g 13000 abc จะเปลี่ยนหมายเลขกลุ่ม abc เป็น 13000 เพื่อให้ abc เขียนไฟล์ด้วย GID เดียวกับกลุ่ม media บนโฮสต์ ทุกคอนเทนเนอร์ใน stack จะรักษา PUID ของตนเองไว้แต่ใช้ PGID เดียวกัน ซึ่งรวมถึงคอนเทนเนอร์ที่อยู่ถัดไปในห่วงโซ่ที่ทำหน้าที่อ่านคลังไฟล์ที่เสร็จสมบูรณ์เท่านั้น เช่น Jellyfin และส่วนหน้า (front end) ที่เชื่อมต่อเข้ามา เช่น Halcyon ซึ่งให้บริการคลังไฟล์นั้นในรูปแบบร้านวิดีโอสไตล์ยุค 90

จากนั้นตั้งค่า UMASK=002 ในทุกคอนเทนเนอร์ของ linuxserver ใน stack นี่คือขั้นตอนที่หลายคนมักมองข้าม ค่าเริ่มต้นในอิมเมจเหล่านี้คือ 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

ค่าทั้งสองนี้ควรอยู่ในไฟล์ .env ที่วางไว้ข้างไฟล์ Compose เพื่อให้ทั้ง stack อ่านนิยามเดียวกัน:

PUID=1000
PGID=13000

Compose จะอ่านไฟล์นั้นโดยอัตโนมัติสำหรับการแทนที่ค่าแบบ ${PUID} ซึ่งเป็นกลไกเดียวกับที่คุณใช้สำหรับข้อมูลรับรอง (credentials) แนวปฏิบัติเรื่อง การแยกค่าต่างๆ ออกจาก docker-compose.yml ไปไว้ในไฟล์ .env สามารถนำมาใช้ในกรณีนี้ได้เช่นกัน โดยมีความแตกต่างตรงที่ตัวเลขสองค่านี้ไม่ใช่ความลับ

ตรวจสอบผลลัพธ์ตั้งแต่ต้นจนจบแทนการเชื่อใจเพียงแค่การตั้งค่า ให้ลองเขียนไฟล์จากภายในคอนเทนเนอร์หนึ่งแล้วอ่านจากโฮสต์:

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

ผลลัพธ์ที่ถูกต้องจะแสดง PUID ของคุณเป็นเจ้าของ, 13000 เป็นกลุ่ม และ -rw-rw-r-- เป็นโหมดสิทธิ์ หากกลุ่มแสดงเป็น 1000 แสดงว่า setgid bit หายไปจากไดเรกทอรีนั้น หากโหมดแสดงเป็น -rw-r--r-- แสดงว่าตัวแปร UMASK ไม่มีผล ให้ตรวจสอบว่าคุณได้ทำการสร้างคอนเทนเนอร์ใหม่ (recreate) แทนการรีสตาร์ทเพียงอย่างเดียว ลบไฟล์ทดสอบด้วย rm /srv/media/downloads/permtest เมื่อเสร็จสิ้น

อิมเมจใดใช้ตัวแปรใดบ้าง

อิมเมจจาก linuxserver.io ใช้ PUID, PGID และ UMASK ส่วน Paperless-ngx ใช้ชื่อเรียกอื่นสำหรับแนวคิดเดียวกันคือ USERMAP_UID และ USERMAP_GID ซึ่งทั้งคู่มีค่าเริ่มต้นเป็น 1000 และเอกสารประกอบแนะนำให้คุณอ่านค่าจาก id -u และ id -g สำหรับเซิร์ฟเวอร์รูปภาพก็มีความหลากหลายเช่นกัน โดย PhotoPrism มีคู่ PHOTOPRISM_UID และ PHOTOPRISM_GID ของตนเอง ในขณะที่ Immich ไม่มีตัวแปรเทียบเท่าและปล่อยให้ผู้ใช้ในคอนเทนเนอร์เป็นไปตามคีย์ user: ของ Docker ดังนั้นการ เลือกระหว่าง PhotoPrism กับ Immich จึงเป็นการตัดสินใจด้วยว่าคุณจะต้องดูแลกลไกใดในบรรดากลไกเหล่านี้สำหรับคลังรูปภาพขนาดใหญ่บนเซิร์ฟเวอร์ของคุณ อิมเมจต้นทางที่เป็นทางการหลายรายการ รวมถึงอิมเมจฐานข้อมูลและเว็บเซิร์ฟเวอร์ทั่วไป จะกำหนดผู้ใช้แบบตายตัวมาให้ตั้งแต่ต้นและคาดหวังให้คุณใช้ user: หรือปล่อยให้เป็นค่าเริ่มต้น การติดตั้งแอปเดี่ยวขนาดเล็กก็ทำให้เกิดคำถามเดียวกัน ดังนั้นเมื่อคุณเริ่ม ติดตั้ง openGym workout tracker แบบ self-hosted จึงควรตรวจสอบก่อนว่าคอนเทนเนอร์นั้นรันด้วยผู้ใช้ใดก่อนที่คุณจะกำหนด bind mount เข้าไป เพราะไดเรกทอรีที่เก็บฐานข้อมูลจะสืบทอดสิทธิ์นั้นไปไม่ว่าคุณจะตั้งค่า PUID ไว้หรือไม่ก็ตาม สำหรับรีเลย์การเข้าถึงระยะไกลก็จัดอยู่ในหมวดหมู่เดียวกัน ดังนั้นเมื่อ คุณรัน RustDesk relay server ของตัวเอง คู่กุญแจ Ed25519 ที่ hbbs สร้างขึ้นในการเริ่มทำงานครั้งแรกจะปรากฏใน bind mount ของคุณโดยมีเจ้าของเป็นผู้ใช้ที่อิมเมจนั้นกำหนดไว้ และการใช้ chown ที่ฝั่งโฮสต์เป็นวิธีแก้ไขเดียวที่คุณทำได้ หลักการเดียวกันนี้ใช้กับโครงสร้างพื้นฐานที่คุณเพิ่มเข้ามาในภายหลัง ดังนั้นการ ใช้ Authentik เพื่อจัดการการล็อกอินแอปต่างๆ ของคุณ หมายถึงการรันอิมเมจเซิร์ฟเวอร์, Postgres และ Redis อย่างเป็นทางการซึ่งไม่ได้อ่านค่า PUID เลย และความเป็นเจ้าของของ volume จะมาจาก runtime แทนที่จะมาจาก entrypoint ที่คุณกำหนดค่าได้

ดังนั้น โปรดตรวจสอบ README ของแต่ละอิมเมจก่อนที่คุณจะคัดลอกบล็อก environment ไปใช้ระหว่างโปรเจกต์ Docker จะส่งผ่านตัวแปร environment ใดก็ตามที่คุณตั้งค่าเข้าไปในคอนเทนเนอร์ ไม่ว่าจะมีสิ่งใดภายในอ่านค่าเหล่านั้นหรือไม่ก็ตาม และ PUID ที่ไม่มีกระบวนการใดเรียกใช้จะไม่ทำให้เกิดข้อผิดพลาด คำเตือน หรือผลลัพธ์ใดๆ คอนเทนเนอร์จะรันด้วยผู้ใช้ที่กำหนดไว้ใน Dockerfile ของมันเอง และคุณจะทราบได้จากความเป็นเจ้าของไฟล์ที่มันเขียนขึ้นมา โปรดตรวจสอบข้อมูลเหล่านั้นก่อนที่คุณจะเพิ่มสิ่งใหม่ลงในเซิร์ฟเวอร์ รวมถึง การติดตั้ง open-kritt security scanning stack ซึ่งไฟล์ Compose จะบอกคุณว่าอิมเมจเหล่านั้นรองรับ PUID หรือไม่ หรือความเป็นเจ้าของไดเรกทอรีที่คุณ mount ถูกกำหนดไว้อย่างตายตัวโดยตัวอิมเมจเอง

FAQ

ทำไมไฟล์ Docker ของฉันถึงมีเจ้าของเป็น 911:911?

911 คือ UID และ GID ของผู้ใช้ abc ที่ถูกกำหนดไว้ในอิมเมจของ linuxserver.io การที่เห็นเลขนี้หมายความว่าคอนเทนเนอร์เริ่มทำงานโดยไม่ได้ตั้งค่า PUID และ PGID สคริปต์เริ่มต้นจึงใช้ค่าเริ่มต้นที่ฝังมากับอิมเมจ ls -l แสดงเป็นตัวเลขดิบเพราะไม่มีบัญชีผู้ใช้บนโฮสต์ของคุณที่มี ID เป็น 911 จึงไม่มีชื่อมาแสดงผล ให้ตั้งค่า PUID และ PGID ด้วยผลลัพธ์จากคำสั่ง id จากนั้นสร้างคอนเทนเนอร์ใหม่ด้วย docker compose up -d แล้วแก้ไขสิทธิ์ของไฟล์ที่มีอยู่ด้วย sudo chown -R 1000:1000 ในไดเรกทอรีที่ได้รับผลกระทบ

PUID และ PGID ใช้ได้กับ Docker image ทุกตัวหรือไม่?

ไม่ได้ PUID และ PGID ไม่ใช่ฟีเจอร์ของ Docker และ Docker ไม่เคยอ่านค่าเหล่านี้ มันทำงานได้เฉพาะกับอิมเมจที่ entrypoint ของอิมเมจนั้นอ่านค่าและเรียกใช้ usermod และ groupmod ก่อนเริ่มแอปพลิเคชัน ซึ่งได้แก่กลุ่มอิมเมจของ linuxserver.io และบางโปรเจกต์ที่นำรูปแบบนี้ไปใช้ โปรเจกต์อื่นอาจใช้ชื่อเรียกต่างออกไป เช่น USERMAP_UID และ USERMAP_GID ใน paperless-ngx สำหรับอิมเมจที่ไม่ได้อ่านค่าเหล่านี้ ตัวแปรจะถูกรับไว้แต่จะถูกละเลยโดยไม่มีการแจ้งเตือนใดๆ

ฉันควรใช้ PUID และ PGID หรือใช้คีย์ user: ใน Docker Compose?

ควรใช้ PUID และ PGID เมื่ออิมเมจรองรับ เพราะ entrypoint จะยังคงรันในฐานะ root นานพอที่จะแก้ไข /config และเริ่มบริการของตนเองได้อย่างถูกต้อง ให้ใช้ user: เมื่ออิมเมจไม่รองรับ PUID หรือเมื่อ README ของอิมเมจระบุว่าผ่านการทดสอบสำหรับการรันแบบ non-root แล้ว สำหรับอิมเมจของ linuxserver การตั้งค่า user: จะทำให้ PUID และ PGID ไม่มีผล หยุดการทำงานของ Docker Mods และบริการเสริมต่างๆ และทำให้สิทธิ์ของทุก volume ที่ mount เข้ามากลายเป็นความรับผิดชอบของคุณโดยตรง

Sonarr มี PUID ที่ถูกต้องแล้ว แต่ยังย้ายไฟล์ไม่ได้ เกิดจากอะไร?

ให้ตรวจสอบ 3 สิ่งตามลำดับ ดังนี้: ประการแรก คือ media mount เอง: สคริปต์เริ่มต้นจะทำ chown เฉพาะ /app, /config และ /defaults เท่านั้น ดังนั้น /data หรือ /downloads จะยังคงสิทธิ์เดิมที่มีอยู่บนโฮสต์ ประการที่สอง คือกลุ่มผู้ใช้ร่วม: หากโปรแกรมดาวน์โหลดและ Sonarr รันด้วย GID ที่ต่างกัน ทั้งสองจะไม่สามารถแก้ไขไฟล์ของกันและกันได้ ดังนั้นให้กำหนด PGID เดียวกันให้กับทุกคอนเทนเนอร์ใน stack ประการที่สาม คือ umask: ค่าเริ่มต้นของอิมเมจ UMASK=022 จะเขียนไฟล์ด้วยสิทธิ์ 0644 ซึ่งไม่มีบิตการเขียนสำหรับกลุ่ม (group write bit) ทำให้การใช้กลุ่มร่วมกันไม่ได้ผล ให้ตั้งค่า UMASK=002 และตั้งค่า setgid bit บนไดเรกทอรีด้วย chmod 2775 เพื่อให้ไฟล์ใหม่สืบทอดกลุ่มผู้ใช้มาด้วย