SSD Nodes Learn 🎉 VPS החל מ־$5.50/חודש
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-13

מהם PUID ו-PGID ב-Docker Compose ואיך להגדיר אותם?

PUID ו-PGID אינם הגדרות Docker, אלא מוסכמה של linuxserver.io. גלו מדוע קבצים ב-bind mount נוצרים עם UID 911 ואיך להשתמש בפקודת id כדי להגדיר הרשאות גישה נכונות.

מהם למעשה PUID ו־PGID

PUID ו־PGID הם שני משתני סביבה שאימג'ים מסוימים של מכולות קוראים בעת העלייה. Docker עצמו לעולם אינו בודק אותם. מדובר במוסכמה, המשמשת את האימג'ים של linuxserver.io ועוד קומץ אחרים, לכן אימג' שלא נכתב כדי לקרוא אותם יתעלם מהם בשקט.

בתוך אימג' של linuxserver.io קיים משתמש בשם abc, שנוצר בזמן ה-build עם UID (מזהה משתמש) 911 ו-GID (מזהה קבוצה) 911. המכולה עולה כ-root, מריצה את סקריפטי ה-init שלה, ואחד הסקריפטים הללו משנה את המספר של אותו משתמש לפני שקורה כל דבר אחר:

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

הדגל -o מאפשר שימוש במזהה שכבר נמצא בשימוש במקום אחר. לאחר מכן ה-init מוותר על הרשאות ומריץ את היישום כ-abc. לכן PUID=1000 לעולם לא מגיע ל-Docker. המשתנה משנה את המספר של משתמש בתוך המכולה לפני שהיישום מתחיל, מה שאומר שכל קובץ שהיישום כותב מגיע לדיסק שלכם כשהוא בבעלות 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 מציג מזהה מספרי במקום שם כאשר אין חשבון משתמש במערכת התואם למזהה זה. שום דבר בשרת שלכם אינו בעל 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

תהליך ה-init של linuxserver מדפיס את התוצאה בלוג העלייה בשתי שורות:

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) יש הרשאות קריאה והרצה, אך לא כתיבה. מחיקת קובץ דורשת הרשאת כתיבה על הספרייה שלו, לא על הקובץ עצמו, לכן אתם מקבלים שגיאה זו גם כאשר הקובץ נראה תקין:

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 ריק ומעגן אותו מעל נתיב שקיים ב-image, הוא מעתיק את תוכן הנתיב לתוך ה-volume, כולל הבעלות וסיביות ההרשאות, כך שהיישום מוצא ספרייה שהוא כבר הבעלים שלה. bind mount לא מקבל טיפול כזה: Docker מעגן את ספריית המארח שלכם בדיוק כפי שהיא. הבדל זה הוא אחת הסיבות המעשיות לדעת מתי 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 אם אינכם מעוניינים להקליד את המספרים. בצעו זאת כשהמכולה עצורה, כיוון שיישום פעיל שנמצא באמצע תהליך כתיבה במהלך chown רקורסיבי עלול להסתיים עם עץ ספריות מתוקן למחצה וסבב שני ומבלבל של שגיאות.

מה PUID ו-PGID אינם פותרים

זהו החלק שמכשיל משתמשים שביצעו את כל השלבים כראוי. תהליך ה-init של linuxserver מבצע chown לשלושה נתיבים בלבד בעת העלייה: /app, /config ו-/defaults. נקודות העיגון (mounts) של המדיה שלכם אינן ברשימה זו. הנתיבים /data, /downloads ו-/tv מועברים ליישום ללא שינוי. לכן, אם בצד המארח (host) של נקודות עיגון אלו קיימת בעלות שמשתמש המכולה אינו יכול לכתוב אליה, המכולה תעלה בצורה תקינה, תציג את ה-UID הנכון בבאנר שלה, אך תיכשל בניסיון הייבוא הראשון.

זוהי התנהגות תקינה. ביצוע chown רקורסיבי על ספריית מדיה בנפח 12 טרה-בייט בכל הפעלה של מכולה יהיה הרסני. המשמעות היא שהאחריות על תיקיות המדיה מוטלת עליכם, ושם נמצאים נקודות העיגון שבהן הרשאות נוטות להשתבש בפועל.

שלוש דרכים לשליטה במשתמש, ומתי כל אחת מהן רלוונטית

משתני סביבה PUID ו-PGID

שיטה זו עובדת רק על אימג'ים שנקודת הכניסה (entrypoint) שלהם קוראת משתנים אלו. היא פופולרית מכיוון שהמכולה עדיין עולה כ-root, מבצעת את ההגדרות שלה, מתקנת את /config, ורק אז מוותרת על הרשאות. Docker Mods וסקריפטים מותאמים אישית להפעלה ממשיכים לעבוד. החיסרון הוא שאתם מסתמכים על מוסכמה ולא על תכונה מובנית של הפלטפורמה, ושמות המשתנים אינם אחידים בין פרויקטים שונים.

המפתח user: ב-Compose

זוהי תכונה אמיתית של Docker והיא עובדת על כל אימג', מכיוון שסביבת הריצה של המכולה מחילה אותה לפני שהקוד של האימג' עצמו רץ:

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

התהליך לעולם אינו רץ כ-root, אפילו לא לרגע, מה שמהווה שיפור אבטחה אמיתי. עם זאת, זה משבש כל פעולה ב-entrypoint שדרשה הרשאות root. באימג'ים של linuxserver הפרויקט תומך בכך על בסיס מאמץ סביר ורק עבור אימג'ים שנבדקו, והסייגים ספציפיים: PUID ו-PGID מפסיקים להשפיע, Docker Mods לא ירוצו, שירותים מותאמים אישית לא יפעלו, ואתם הופכים לאחראים על ההרשאות בכל כונן שמופה (mounted volume). התבנית המתועדת שלהם משלבת את הדגל עם /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 מצפה ללפחות 65,536 מזהים משניים שם.

קראו את המיפוי הזה שוב, כי הוא הופך את ההמלצה הרגילה. תחת rootless Docker, מכולה שכותבת כ-root מייצרת קבצים שבבעלותכם. מכולה שכותבת כ-UID 1000 מייצרת קבצים שבבעלות מזהה משני כלשהו סביב 100999, שה-shell שלכם לא יכול לגשת אליו. לכן, ערך PUID שנכון ב-daemon עם הרשאות root הוא הערך השגוי כאן. שני המנגנונים פותרים את אותה בעיה בשכבות שונות, וערבוב ביניהם ללא בדיקה הוא הסיבה לכך שאנשים נתקעים עם תיקייה שהם צריכים sudo כדי למחוק. אם אתם עוברים ל-rootless, בדקו את הבעלות על קובץ אחד שנכתב בשרת שלכם לפני שאתם מעבירים אליו ספרייה.

עבור רוב הסטאקים של אירוח עצמי על VPS בודד, PUID ו-PGID ב-daemon עם הרשאות root הם הבחירה הפרגמטית, מכיוון שזה מה שהאימג'ים בנויים ומתועדים עבורו. השתמשו ב-user: כאשר ה-README של האימג' מציין שהוא נבדק עבורו, או כאשר אתם מריצים אימג' רשמי שאין לו תמיכה ב-PUID כלל. סביבת עבודה של מסמכים כמו מופע AFFiNE באירוח עצמי על VPS נופלת למקרה האחרון, מכיוון שאף אחד מהקונטיינרים שלה לא קורא PUID והבעלות על תיקיית מסד הנתונים והקבצים שהועלו נקבעת על ידי סביבת הריצה ולא על ידי משהו בבלוק הסביבה.

המקרה של מחסנית מדיה: קבוצה אחת משותפת בין מכולות

מחסנית מדיה מסוג arr עם Sonarr, Radarr ולקוח הורדות היא המקום שבו הנושא מפסיק להיות תיאורטי. לקוח ההורדות כותב קובץ שהושלם לתוך /data/downloads. לאחר מכן, Sonarr מבצע hardlink או מעביר את הקובץ לתוך /data/media. כדי שפעולת ה-hardlink תצליח, שתי המכולות זקוקות להרשאות כתיבה לאותו עץ תיקיות. אם לקוח ההורדות רץ כ-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 {} +

ה-2 המוביל ב-2775 הוא ה-setgid bit. בתיקייה, המשמעות היא שכל קובץ או תת-תיקייה חדשים שנוצרים בתוכה יורשים את הקבוצה media במקום את הקבוצה הראשית של יוצר הקובץ. כך ההגדרה נשמרת גם עבור הורדות חדשות, מבלי שתצטרכו להריץ שוב את chown. התנתקו מהמערכת והתחברו מחדש, או הריצו את newgrp media, לפני שתבדקו את הגישה שלכם: קבוצה שנוספה באמצעות usermod -aG לא מופיעה ב-shell שכבר פתוח.

בתוך המכולה, groupmod -o -g 13000 abc משנה את המספר של הקבוצה abc ל-13000, כך ש-abc כותב עם אותו GID של קבוצת ה-media במארח. כל מכולה במחסנית שומרת על ה-PUID הייחודי שלה ומשתפת את אותו ה-PGID.

לאחר מכן, הגדירו את UMASK=002 בכל מכולת linuxserver במחסנית. זהו השלב שאנשים מפספסים. ברירת המחדל בתמונות אלו היא 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, כך שכל המחסנית תקרא הגדרה אחת:

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-- כמצב ההרשאות (mode). אם הקבוצה מציגה 1000, סימן שה-setgid bit חסר בתיקייה. אם מצב ההרשאות מציג -rw-r--r--, משתנה ה-UMASK לא נכנס לתוקף; בדקו שיצרתם מחדש את המכולה במקום רק לבצע לה restart. מחקו את קובץ הבדיקה עם 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: או שתשאירו אותו כפי שהוא. אותו הדבר תקף לתשתיות שמוסיפים מאוחר יותר; לכן, הטמעת Authentik לפני היישומים שלכם עבור כניסה יחידה משמעותה הרצת תמונות רשמיות של שרת, Postgres ו-Redis שאינן קוראות כלל PUID, והבעלות על ה-volumes נקבעת בזמן הריצה ולא דרך entrypoint שניתן להגדרה.

לכן, בדקו את ה-README של כל תמונה לפני שאתם מעתיקים בלוק משתני סביבה בין פרויקטים. Docker מעביר כל משתנה סביבה שתגדירו לתוך כל מכולה, בין אם משהו בתוכה קורא אותו ובין אם לא. משתנה PUID שאף תהליך לא צורך לא יפיק שגיאה, לא אזהרה ולא השפעה. המכולה תרוץ תחת המשתמש שבו הסתיים ה-Dockerfile שלה, ואתם תגלו זאת דרך הבעלות על הקבצים שהיא כותבת.

FAQ

מדוע הקבצים של ה-Docker שלי שייכים ל-911:911?

911 הוא ה-UID וה-GID של המשתמש abc המובנה בתמונות של linuxserver.io. הופעת מספר זה מעידה על כך שהמכולה הופעלה ללא הגדרת PUID ו-PGID, ולכן סקריפט האתחול שלה השאיר את ברירות המחדל המובנות. ls -l מציג את המספרים הגולמיים כיוון שאין אף חשבון במערכת המארחת עם מזהה 911, ולכן אין שם להציג. הגדירו את PUID ואת PGID לפי הפלט של id, צרו מחדש את המכולה בעזרת docker compose up -d, ולאחר מכן תקנו את הרשאות הקבצים הקיימים בעזרת sudo chown -R 1000:1000 על הספרייה הרלוונטית.

האם PUID ו-PGID עובדים בכל תמונת Docker?

לא. הם אינם תכונה של 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 של התמונה מציין שהיא נבדקה להרצה ללא הרשאות root. בתמונה של linuxserver, הגדרת user: הופכת את PUID ו-PGID ללא פעילים, מונעת מ-Docker Mods ושירותים מותאמים אישית לרוץ, ומטילה עליכם את האחריות להרשאות של כל ה-volumes המותקנים.

ל-Sonarr יש את ה-PUID הנכון אך הוא עדיין לא מצליח להעביר קבצים. מה הבעיה?

בדקו שלושה דברים לפי הסדר. ראשית, את ה-mount של המדיה עצמו: האתחול מבצע chown רק ל-/app, /config ו-/defaults, לכן /data או /downloads שומרים על הבעלות הקיימת שלהם במערכת המארחת. שנית, הקבוצה המשותפת: אם לקוח ההורדות ו-Sonarr רצים תחת GIDs שונים, אף אחד מהם לא יוכל לשנות את הקבצים של האחר, לכן תנו לכל מכולה ב-stack את אותו PGID. שלישית, ה-umask: ברירת המחדל של התמונה UMASK=022 כותבת קבצים כ-0644 ללא ביט כתיבה לקבוצה, מה שמבטל לחלוטין את האפקטיביות של קבוצה משותפת. הגדירו UMASK=002 והגדירו את ה-setgid bit על הספריות בעזרת chmod 2775 כדי שקבצים חדשים יירשו את הקבוצה.