کاربرد PUID و PGID در Docker Compose چیست؟
متغیرهای PUID و PGID تنظیمات داکر نیستند بلکه قرارداد ایمیجهای linuxserver.io هستند. با تنظیم صحیح این مقادیر، مشکل مالکیت فایلها با UID 911 در bind mount را رفع کنید.
PUID و PGID دقیقاً چه هستند
PUID و PGID دو متغیر محیطی هستند که برخی ایمیجهای کانتینر در زمان شروع (startup) آنها را میخوانند. خود 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 امتیازات (privileges) را کاهش داده و برنامه را با کاربر abc اجرا میکند. بنابراین PUID=1000 هرگز به Docker نمیرسد. این متغیر، کاربر داخل کانتینر را پیش از شروع برنامه دوباره شمارهگذاری میکند؛ این یعنی هر فایلی که آن برنامه مینویسد، روی دیسک شما با مالکیت 1000 ذخیره میشود. اگر PUID را تنظیم نکنید، abc همان 911 باقی میماند و به همین دلیل است که یک bind mount پیکربندینشده، پر از فایلهایی با مالکیت 911:911 میشود.
دریافت دو عدد خود با id
این دستور را روی میزبان، با کاربری که مالک دایرکتوریهای داده است، اجرا کنید:
iduid=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/sonarrdrwxr-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اگر پس از تنظیم PUID=1000 در فایل Compose، آن خطوط عدد 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 باشد و برنامه با UID 911 اجرا شود، اولین تلاش آن برای نوشتن با خطای Permission denied مواجه شده و برنامه آن را با پیام خاص خود گزارش میدهد. در برنامههای .NET مانند Sonarr یا Radarr، این خطا به صورت UnauthorizedAccessException: Access to the path '/data/downloads' is denied ظاهر میشود. رشته مجوزها در ابتدای فایل به شما میگوید که بر اساس کدامیک از سه مجموعه مجوز مورد قضاوت قرار میگیرید، و خواندن صحیح drwxr-xr-x همان چیزی است که این خطای مبهم را به موضوعی بدیهی تبدیل میکند.
این مشکل بهطور خاص مربوط به bind mount است. هنگامی که Docker یک named volume خالی ایجاد میکند و آن را روی مسیری که در image وجود دارد mount میکند، محتویات آن مسیر، شامل مالکیت و بیتهای مجوز را به 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 اصلاح نمیکنند
این بخشی است که کاربران با وجود انجام صحیح تمام مراحل، در آن دچار مشکل میشوند. اسکریپت init در ایمیجهای linuxserver هنگام راهاندازی، مالکیت (chown) دقیقاً سه مسیر را تغییر میدهد: /app، /config و /defaults. مانتهای رسانهای (media mounts) شما در این لیست قرار ندارند. مسیرهای /data، /downloads و /tv بدون تغییر به برنامه تحویل داده میشوند؛ بنابراین اگر سمت میزبان (host) این مانتها دارای مالکیتی باشد که کاربر داخل کانتینر اجازه نوشتن در آن را نداشته باشد، کانتینر بهدرستی بالا میآید، UID صحیح را در بنر خود نمایش میدهد، اما در اولین تلاش برای import با خطا مواجه میشود.
این رفتار صحیح است. اجرای یک chown بازگشتی (recursive) روی یک کتابخانه رسانهای 12 ترابایتی در هر بار راهاندازی کانتینر، یک فاجعه خواهد بود. این یعنی مدیریت دایرکتوریهای رسانه بر عهده شماست و اینها همان نقاط مانت هستند که مجوزها در آنها واقعاً دچار مشکل میشوند.
Three ways to control the user, and when each one applies
PUID and PGID environment variables
This works only on images whose entrypoint reads them. It is popular because the container still starts as root, does its own setup, fixes /config, and only then drops privileges. Docker Mods and custom init scripts keep working. The cost is that you are trusting a convention rather than a platform feature, and the variable names are not standard across projects.
The user: key in Compose
This one is a real Docker feature and works on every image, because the container runtime applies it before the image's own code runs:
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"The process never runs as root, not even for a moment, which is a genuine security gain. It also breaks anything in the entrypoint that needed root. On linuxserver images the project supports this on a reasonable endeavours basis and only for images it has tested, and the caveats are specific: PUID and PGID stop having any effect, Docker Mods will not run, custom services will not run, and you become responsible for the permissions on every mounted volume. Their documented pattern pairs the flag with a writable /run:
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=trueOne cosmetic side effect surprises people. A numeric user: has no matching entry in the container's /etc/passwd, so tools inside report whoami: cannot find name for user ID 1000. The ID is valid and file access works normally. Only the name lookup fails.
Rootless Docker
Rootless Docker runs the daemon itself as your unprivileged user, so nothing on the box runs as real root. It changes the ownership arithmetic completely. Container UID 0 maps to the host UID of the user running rootless Docker, and container UID n for any n of 1 or more maps to subuid + (n - 1), where subuid is the base of the range allocated to you in /etc/subuid and /etc/subgid. Docker expects at least 65,536 subordinate IDs there.
Read that mapping again, because it inverts the usual advice. Under rootless Docker a container writing as root produces files owned by you. A container writing as UID 1000 produces files owned by a subordinate ID somewhere around 100999, which your shell cannot touch. So the PUID value that is correct on a rootful daemon is the wrong one here. The two mechanisms solve the same problem in different layers, and stacking them without checking is how people end up with a directory they need sudo to remove. If you go rootless, test the ownership of one written file on your own server before you migrate a library into it.
For most self-hosted stacks on a single VPS, PUID and PGID on a rootful daemon is the pragmatic choice, because it is what the images are built and documented for. Reach for user: when the image README says that image is tested for it, or when you are running an official upstream image that has no PUID support at all. A document workspace such as a self-hosted AFFiNE instance on one VPS falls into that last case, because none of its containers read PUID and the ownership of its database directory and uploaded files is settled by the runtime rather than by anything in the environment block.
سناریوی پشته رسانهای: یک گروه مشترک بین کانتینرها
یک پشته رسانهای arr شامل Sonarr، Radarr و یک کلاینت دانلود جایی است که این مباحث از حالت تئوری خارج میشوند. کلاینت دانلود، فایل تکمیلشده را در /data/downloads مینویسد. سپس Sonarr آن فایل را به /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 است. روی یک دایرکتوری، این یعنی هر فایل و زیردایرکتوری جدیدی که در آن ایجاد شود، به جای گروه اصلیِ ایجادکننده، گروه media را به ارث میبرد. بنابراین، این تنظیمات با دانلودهای جدید نیز حفظ میشود و نیازی به اجرای مجدد chown نیست. پیش از بررسی دسترسی خود، از سیستم خارج و دوباره وارد شوید یا newgrp media را اجرا کنید؛ چرا که گروه اضافهشده با usermod -aG در نشستهای (session) شل که از قبل باز هستند، نمایش داده نمیشود.
در داخل کانتینر، groupmod -o -g 13000 abc گروه abc را به 13000 تغییر شماره میدهد تا abc با همان GID گروه media در میزبان (host) بنویسد. هر کانتینر در پشته، 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=13000Compose این فایل را بهطور خودکار برای جایگزینی متغیرها به سبک ${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 در آن دایرکتوری اعمال نشده است. اگر حالت (mode) برابر -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: استفاده کنید یا آن را به حال خود رها کنید. همین موضوع در مورد زیرساختهایی که بعداً اضافه میکنید نیز صدق میکند؛ بنابراین قرار دادن Authentik جلوی برنامهها برای ورود یکپارچه به معنای اجرای ایمیجهای رسمی سرور، Postgres و Redis است که اصلاً PUID را نمیخوانند و مالکیت volume آنها به جای entrypoint قابل پیکربندی، توسط زمان اجرا (runtime) تعیین میشود.
بنابراین پیش از کپی کردن یک بلوک environment بین پروژهها، README هر ایمیج را بررسی کنید. Docker هر متغیر محیطی که تنظیم کنید را به داخل کانتینر میفرستد، صرفنظر از اینکه چیزی در داخل آن را میخواند یا خیر؛ و یک PUID که توسط هیچچیز مصرف نمیشود، نه خطا ایجاد میکند، نه هشدار و نه هیچ تأثیری دارد. کانتینر با هر کاربری که Dockerfile آن در نهایت با آن تمام شده است اجرا میشود و شما این موضوع را از طریق مالکیت فایلهایی که مینویسد متوجه خواهید شد.
FAQ
چرا مالکیت فایلهای Docker من 911:911 است؟
911 شناسه کاربری (UID) و شناسه گروه (GID) کاربر abc است که در ایمیجهای linuxserver.io تعبیه شده است. مشاهده این اعداد به این معنی است که کانتینر بدون تنظیم PUID و PGID اجرا شده و اسکریپت اولیه (init script)، مقادیر پیشفرض را تغییر نداده است. دستور 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 پشتیبانی میکند، از PUID و PGID استفاده کنید، زیرا نقطه ورود همچنان به اندازه کافی با دسترسی root اجرا میشود تا /config را اصلاح کرده و سرویسهای خود را بهدرستی راهاندازی کند. زمانی که ایمیج از PUID پشتیبانی نمیکند یا در فایل README آن ذکر شده که برای اجرای غیر root تست شده است، از user: استفاده کنید. در ایمیجهای linuxserver، تنظیم user: باعث میشود PUID و PGID بیاثر شوند، اجرای Docker Mods و سرویسهای سفارشی متوقف گردد و مسئولیت مجوزهای تمام volumeهای mount شده مستقیماً بر عهده شما قرار گیرد.
Sonarr دارای PUID صحیح است اما همچنان نمیتواند فایلها را جابهجا کند. مشکل چیست؟
سه مورد را به ترتیب بررسی کنید. اول، خودِ mount رسانه: اسکریپت اولیه فقط مالکیت /app، /config و /defaults را تغییر میدهد، بنابراین /data یا /downloads مالکیت فعلی خود را روی میزبان حفظ میکنند. دوم، گروه اشتراکی: اگر کلاینت دانلود و Sonarr با GIDهای متفاوتی اجرا شوند، هیچکدام نمیتوانند فایلهای دیگری را تغییر دهند؛ بنابراین به تمام کانتینرهای موجود در stack، مقدار PGID یکسانی اختصاص دهید. سوم، umask: مقدار پیشفرض UMASK=022 در ایمیج، فایلها را با مجوز 0644 و بدون بیت نوشتن گروه (group write bit) ایجاد میکند که عملاً کارایی گروه اشتراکی را از بین میبرد. مقدار UMASK=002 را تنظیم کنید و بیت setgid را با دستور chmod 2775 روی دایرکتوریها اعمال کنید تا فایلهای جدید، گروه را به ارث ببرند.