PUID i PGID w Docker Compose: jak działają?
PUID i PGID nie są ustawieniami Dockera, lecz konwencją obrazów linuxserver.io. Dowiedz się, dlaczego pliki bind mount mają właściciela 911 i jak poprawnie skonfigurować UID.
Czym w rzeczywistości są PUID i PGID
PUID i PGID to dwie zmienne środowiskowe odczytywane przez niektóre obrazy kontenerów podczas uruchamiania. Sam Docker nigdy ich nie sprawdza. Są one konwencją stosowaną przez obrazy linuxserver.io oraz nieliczne inne, dlatego obraz, który nie został przygotowany do ich obsługi, zignoruje je bez żadnego komunikatu.
Wewnątrz obrazu linuxserver.io znajduje się użytkownik o nazwie abc, utworzony na etapie budowania z UID (identyfikatorem użytkownika) 911 oraz GID (identyfikatorem grupy) 911. Kontener startuje z uprawnieniami root, wykonuje swoje skrypty inicjalizacyjne, a jeden z nich zmienia numerację tego użytkownika, zanim wydarzy się cokolwiek innego:
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abcFlaga -o pozwala na użycie identyfikatora, który jest już wykorzystywany w innym miejscu. Następnie proces inicjalizacji porzuca uprawnienia i uruchamia aplikację jako abc. Zatem PUID=1000 nigdy nie dociera do Dockera. Zmienna ta zmienia numerację użytkownika wewnątrz kontenera przed uruchomieniem aplikacji, co oznacza, że każdy plik zapisany przez tę aplikację trafia na dysk jako własność użytkownika o identyfikatorze 1000. Pozostawienie PUID bez wartości sprawia, że abc zachowuje 911, co jest przyczyną, dla której nieskonfigurowany bind mount zapełnia się plikami należącymi do 911:911.
Uzyskanie dwóch identyfikatorów za pomocą id
Uruchom poniższe polecenie na hoście, jako użytkownik będący właścicielem katalogów z danymi:
iduid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)uid to Twój PUID, a gid to Twój PGID. W przypadku skryptu, id -u oraz id -g wyświetlą same liczby. Na większości świeżych obrazów VPS pierwsze konto użytkownika ma identyfikator 1000:1000, jednak nie należy przyjmować tego za pewnik. Przebudowany serwer lub drugie konto dodane w późniejszym czasie otrzymają wartość 1001 lub wyższą, a błędna wartość w tym miejscu jest przyczyną całego problemu. Jeśli usługi działają w ramach dedykowanego konta serwisowego zamiast Twojego własnego użytkownika, uruchom id thatuser i pobierz identyfikatory z tego poziomu.
Dlaczego pliki są wyświetlane jako 911:911
ls -l wyświetla identyfikator numeryczny zamiast nazwy, gdy żaden lokalny użytkownik nie odpowiada danemu ID. Na serwerze nie istnieje użytkownik o UID 911, więc system nie ma nazwy do wyświetlenia. Należy użyć ls -ln, aby wymusić wyświetlanie liczb i wyeliminować niejednoznaczność:
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.xmlPowyższy wynik oznacza, że kontener został uruchomiony z domyślnymi ustawieniami. Należy potwierdzić to z wnętrza kontenera, zamiast polegać na domysłach:
docker exec sonarr id abc
docker compose logs sonarr | head -n 25Proces inicjalizacji linuxserver wyświetla wynik w dzienniku uruchamiania w dwóch liniach:
User UID: 911
User GID: 911Jeśli po ustawieniu PUID=1000 w pliku Compose linie te nadal wskazują 911, zmienna nie została przekazana do kontenera. Najczęstszą przyczyną jest edycja pliku docker-compose.yml i wykonanie docker compose restart, co powoduje ponowne użycie istniejącego kontenera z jego pierwotnym środowiskiem. Zmiany w zmiennych środowiskowych wymagają użycia docker compose up -d, co wymusza odtworzenie kontenera.
Dlaczego nie można usunąć pliku utworzonego przez kontener
Jądro systemu porównuje identyfikatory numeryczne, a nie nazwy. Powłoka użytkownika działa z UID 1000. Plik należy do UID 911. Katalog, w którym się znajduje, to drwxr-xr-x i również należy do 911, więc grupy i pozostali użytkownicy mają uprawnienia do odczytu i wykonywania, ale nie do zapisu. Usunięcie pliku wymaga uprawnień do zapisu w jego katalogu nadrzędnym, a nie w samym pliku, dlatego błąd występuje nawet wtedy, gdy sam plik wydaje się niegroźny:
rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission deniedKontener zapisujący dane napotyka ten sam problem z drugiej strony. Jeśli katalog na hoście należy do użytkownika z trybem 755, a aplikacja działa jako 911, pierwsza próba zapisu kończy się błędem Permission denied, który aplikacja zgłasza w swoim własnym komunikacie. W aplikacjach .NET, takich jak Sonarr czy Radarr, objawia się to jako UnauthorizedAccessException: Access to the path '/data/downloads' is denied. Ciąg uprawnień widoczny przed nazwą pliku wskazuje, który z trzech zestawów uprawnień jest aktualnie brany pod uwagę, a poprawna interpretacja drwxr-xr-x sprawia, że ten błąd przestaje być zagadką i staje się oczywisty.
Jest to problem specyficzny dla bind mount. Gdy Docker tworzy pusty wolumen nazwany i montuje go w ścieżce istniejącej w obrazie, kopiuje zawartość tej ścieżki do wolumenu, zachowując właściciela i bity uprawnień, dzięki czemu aplikacja otrzymuje katalog, którego jest już właścicielem. Bind mount nie podlega takiemu procesowi: Docker montuje katalog hosta dokładnie w takiej postaci, w jakiej się znajduje. Ta różnica jest jednym z praktycznych powodów, dla których warto wiedzieć, kiedy bind mount jest lepszy od wolumenu nazwanego, a kiedy nie.
Naprawa nieprawidłowo skonfigurowanego katalogu
Ustawienie PUID oraz PGID zmienia zachowanie aplikacji od tego momentu. Nie naprawia to jednak wstecznie plików już znajdujących się na dysku. Należy zatrzymać stos, samodzielnie poprawić uprawnienia, a następnie ponownie uruchomić usługę:
docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -dUżyj sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr, jeśli chcesz uniknąć ręcznego wpisywania identyfikatorów. Wykonaj to przy zatrzymanym kontenerze, ponieważ aplikacja zapisująca dane w trakcie rekurencyjnego polecenia chown może doprowadzić do powstania częściowo poprawionego drzewa katalogów i wygenerować mylące błędy w kolejnej próbie.
Czego PUID i PGID nie naprawiają
To jest fragment, który sprawia trudności użytkownikom, którzy wykonali wszystkie kroki poprawnie. Skrypt inicjujący linuxserver zmienia właściciela dokładnie trzech ścieżek podczas uruchamiania: /app, /config oraz /defaults. Twoje punkty montowania mediów nie znajdują się na tej liście. Ścieżki /data, /downloads oraz /tv są przekazywane do aplikacji w stanie nienaruszonym. Jeśli zatem strona hosta tych montowań posiada uprawnienia, do których użytkownik kontenera nie ma dostępu, kontener uruchomi się poprawnie, wyświetli właściwy UID w swoim banerze, a następnie zawiedzie przy pierwszej próbie importu.
Jest to poprawne zachowanie. Rekurencyjne wykonanie chown na dwunastoterabajtowej bibliotece mediów przy każdym starcie kontenera byłoby katastrofą. Oznacza to, że za katalogi z mediami odpowiadasz samodzielnie i to właśnie tam najczęściej dochodzi do problemów z uprawnieniami.
Trzy sposoby kontroli użytkownika i zastosowanie każdego z nich
Zmienne środowiskowe PUID i PGID
Metoda ta działa tylko w obrazach, których punkt wejścia (entrypoint) odczytuje te zmienne. Jest popularna, ponieważ kontener nadal uruchamia się jako root, wykonuje własną konfigurację, naprawia /config i dopiero wtedy obniża uprawnienia. Docker Mods oraz niestandardowe skrypty inicjalizacyjne działają bez zmian. Kosztem jest poleganie na konwencji, a nie na funkcji platformy, a nazwy zmiennych nie są ujednolicone w różnych projektach.
Klucz user: w Compose
Jest to rzeczywista funkcja Docker, która działa z każdym obrazem, ponieważ środowisko uruchomieniowe kontenera stosuje ją przed wykonaniem kodu zawartego w obrazie:
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"Proces nigdy nie działa jako root, nawet przez chwilę, co stanowi realny zysk w zakresie bezpieczeństwa. Powoduje to jednak przerwanie działania każdego elementu w punkcie wejścia, który wymagał uprawnień roota. W obrazach linuxserver projekt wspiera to rozwiązanie w ramach najlepszych starań i tylko dla przetestowanych obrazów. Istnieją konkretne zastrzeżenia: PUID i PGID przestają działać, Docker Mods nie zostaną uruchomione, niestandardowe usługi nie wystartują, a użytkownik staje się odpowiedzialny za uprawnienia do wszystkich zamontowanych wolumenów. Udokumentowany wzorzec łączy tę flagę z zapisywalnym /run:
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=trueJeden kosmetyczny efekt uboczny bywa zaskakujący. Numeryczny user: nie posiada odpowiadającego wpisu w /etc/passwd kontenera, więc narzędzia wewnątrz zgłaszają whoami: cannot find name for user ID 1000. Identyfikator jest poprawny, a dostęp do plików działa normalnie. Nie udaje się jedynie rozpoznanie nazwy użytkownika.
Rootless Docker
Rootless Docker uruchamia samą usługę demona jako użytkownik bez uprawnień, dzięki czemu nic na serwerze nie działa jako rzeczywisty root. Całkowicie zmienia to arytmetykę własności. UID 0 w kontenerze mapuje się na UID hosta użytkownika uruchamiającego Rootless Docker, a UID kontenera n dla każdego n równego 1 lub więcej mapuje się na subuid + (n - 1), gdzie subuid jest podstawą zakresu przydzielonego w /etc/subuid oraz /etc/subgid. Docker oczekuje tam co najmniej 65,536 podrzędnych identyfikatorów.
Należy ponownie przeanalizować to mapowanie, ponieważ odwraca ono standardowe zalecenia. W Rootless Docker kontener zapisujący jako root tworzy pliki należące do użytkownika hosta. Kontener zapisujący jako UID 1000 tworzy pliki należące do podrzędnego identyfikatora w okolicach 100999, do których powłoka użytkownika nie ma dostępu. Zatem wartość PUID poprawna dla standardowego demona Docker jest tutaj błędna. Oba mechanizmy rozwiązują ten sam problem na różnych warstwach, a ich łączenie bez weryfikacji prowadzi do powstania katalogów, których usunięcie wymaga sudo. W przypadku przejścia na Rootless Docker należy przetestować własność zapisanego pliku na własnym serwerze przed migracją biblioteki danych.
Dla większości samodzielnie hostowanych stosów na pojedynczym VPS, użycie PUID i PGID przy standardowym demonie Docker jest wyborem pragmatycznym, ponieważ to właśnie do tego obrazy są budowane i dokumentowane. Należy sięgnąć po user:, gdy plik README obrazu informuje o jego przetestowaniu pod tym kątem lub gdy uruchamiana jest oficjalna wersja upstream, która w ogóle nie obsługuje PUID. Przestrzeń robocza dokumentów, taka jak samodzielnie hostowana instancja AFFiNE na jednym VPS, wpada do tej drugiej kategorii, ponieważ żaden z jej kontenerów nie odczytuje PUID, a własność katalogu bazy danych oraz przesłanych plików jest ustalana przez środowisko uruchomieniowe, a nie przez cokolwiek w bloku środowiskowym.
Przypadek stosu multimedialnego: jedna grupa współdzielona przez kontenery
Stos multimedialny arr z Sonarr, Radarr i klientem pobierania to miejsce, w którym teoria spotyka się z praktyką. Klient pobierania zapisuje gotowy plik w /data/downloads. Następnie Sonarr tworzy twarde dowiązanie (hardlink) lub przenosi ten plik do /data/media. Aby twarde dowiązanie zadziałało, oba kontenery muszą mieć uprawnienia do zapisu w tym samym drzewie katalogów. Jeśli klient pobierania działa jako 1000, a Sonarr jako 1001, jeden z nich jest właścicielem plików, a drugi może je tylko odczytać.
Rozwiązaniem jest współdzielona grupa, której każdy kontener w stosie używa jako swojego 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 {} +Wiodące 2 w 2775 to bit setgid. W przypadku katalogu oznacza on, że każdy nowy plik i podkatalog utworzony wewnątrz dziedziczy grupę media zamiast głównej grupy twórcy. Dzięki temu konfiguracja pozostaje skuteczna przy nowych pobraniach bez konieczności ponownego uruchamiania chown. Wyloguj się i zaloguj ponownie lub wykonaj newgrp media przed sprawdzeniem własnego dostępu: grupa dodana za pomocą usermod -aG nie pojawia się w już otwartej sesji powłoki.
Wewnątrz kontenera groupmod -o -g 13000 abc zmienia numer grupy abc na 13000, dzięki czemu abc zapisuje pliki z tym samym GID, co grupa media na hoście. Każdy kontener w stosie zachowuje własny PUID i współdzieli ten jeden PGID.
Następnie ustaw UMASK=002 w każdym kontenerze linuxserver w stosie. Jest to krok, który często bywa pomijany. Domyślną wartością w tych obrazach jest UMASK=022, która usuwa bit zapisu dla grupy z każdego nowego pliku. W rezultacie pliki otrzymują uprawnienia 0644 i współdzielenie, które właśnie skonfigurowano, nie działa. 002 tworzy pliki 0664 oraz katalogi 0775, co pozwala grupie na zapis:
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-stoppedTe dwie wartości należy umieścić w pliku .env obok pliku Compose, aby cały stos korzystał z jednej definicji:
PUID=1000
PGID=13000Compose odczytuje ten plik automatycznie w celu podstawienia zmiennych w stylu ${PUID}, co jest tym samym mechanizmem, którego używasz do przechowywania poświadczeń. Zasady dotyczące przechowywania wartości poza plikiem docker-compose.yml w pliku .env mają tu zastosowanie, z tą różnicą, że te dwie liczby nie są danymi poufnymi.
Zweryfikuj poprawność konfiguracji od początku do końca, zamiast polegać na założeniach. Utwórz plik z wnętrza jednego kontenera i odczytaj go z poziomu hosta:
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtestPoprawny wynik wskazuje Twój PUID jako właściciela, 13000 jako grupę oraz -rw-rw-r-- jako tryb uprawnień. Jeśli grupa ma uprawnienia 1000, oznacza to brak bitu setgid dla tego katalogu. Jeśli tryb wskazuje -rw-r--r--, zmienna UMASK nie została uwzględniona – sprawdź, czy kontener został odtworzony, a nie tylko zrestartowany. Po zakończeniu usuń plik testowy za pomocą rm /srv/media/downloads/permtest.
Które obrazy wykorzystują poszczególne zmienne
Obrazy linuxserver.io korzystają z PUID, PGID oraz UMASK. Paperless-ngx używa innych nazw dla tego samego mechanizmu: USERMAP_UID i USERMAP_GID, przy czym obie domyślnie przyjmują wartość 1000, a dokumentacja zaleca odczytywanie ich z id -u oraz id -g. Serwery zdjęć wykazują podobne zróżnicowanie: PhotoPrism posiada własną parę PHOTOPRISM_UID i PHOTOPRISM_GID, podczas gdy Immich nie dostarcza odpowiednika i pozostawia użytkownika kontenera kluczowi user: w Dockerze, więc wybór między PhotoPrism a Immich determinuje również, który z tych mechanizmów będzie utrzymywany dla największej biblioteki na serwerze. Wiele oficjalnych obrazów typu upstream, w tym popularne obrazy baz danych i serwerów WWW, posiada sztywno zdefiniowanego użytkownika i oczekuje użycia user: lub pozostawienia ustawień domyślnych. To samo dotyczy infrastruktury dodawanej w późniejszym czasie, więc wdrożenie Authentik przed aplikacjami w celu uzyskania pojedynczego logowania oznacza uruchomienie oficjalnych obrazów serwera, Postgres i Redis, które w ogóle nie odczytują PUID, a uprawnienia do wolumenów wynikają z czasu wykonywania, a nie z punktu wejścia (entrypoint), który można skonfigurować.
Przed skopiowaniem bloku zmiennych środowiskowych między projektami należy sprawdzić plik README każdego obrazu. Docker przekazuje każdą ustawioną zmienną środowiskową do kontenera, niezależnie od tego, czy cokolwiek wewnątrz ją odczytuje, a zmienna PUID, która nie jest przez nic wykorzystywana, nie generuje błędu, ostrzeżenia ani żadnego efektu. Kontener uruchamia się jako użytkownik zdefiniowany w jego pliku Dockerfile, a informację o tym można uzyskać poprzez weryfikację uprawnień do zapisywanych przez niego plików.
FAQ
Dlaczego pliki Docker mają właściciela 911:911?
911 to UID i GID użytkownika abc wbudowanego w obrazy linuxserver.io. Taki wynik oznacza, że kontener uruchomił się bez ustawionych zmiennych PUID oraz PGID, więc skrypt inicjujący pozostawił domyślne wartości. ls -l wyświetla surowe numery, ponieważ na hoście nie istnieje konto o ID 911, więc system nie może przypisać nazwy. Ustaw PUID i PGID na wartości zwrócone przez id, odtwórz kontener za pomocą docker compose up -d, a następnie napraw uprawnienia istniejących plików poleceniem sudo chown -R 1000:1000 w odpowiednim katalogu.
Czy PUID i PGID działają w każdym obrazie Docker?
Nie. Nie jest to funkcja Dockera i Docker nigdy ich nie odczytuje. Działają one tylko w obrazach, których punkt wejścia (entrypoint) je odczytuje i wywołuje usermod oraz groupmod przed uruchomieniem aplikacji. Dotyczy to rodziny obrazów linuxserver.io oraz kilku projektów, które skopiowały ten wzorzec. Inne projekty używają odmiennych nazw, takich jak USERMAP_UID i USERMAP_GID w paperless-ngx. W obrazie, który nie obsługuje żadnej z tych zmiennych, zostaną one przyjęte i zignorowane bez żadnego ostrzeżenia.
Czy używać PUID i PGID, czy klucza user: w Docker Compose?
Używaj PUID i PGID, gdy obraz je obsługuje, ponieważ punkt wejścia działa wtedy jako root wystarczająco długo, aby naprawić /config i poprawnie uruchomić usługi. Używaj user:, gdy obraz nie wspiera PUID lub gdy dokumentacja obrazu (README) wskazuje, że jest on przetestowany pod kątem działania bez uprawnień roota. W obrazach linuxserver ustawienie user: sprawia, że PUID i PGID stają się bezużyteczne, blokuje działanie Docker Mods oraz usług niestandardowych, a za uprawnienia wszystkich zamontowanych wolumenów odpowiadasz samodzielnie.
Sonarr ma poprawne PUID, ale nadal nie może przenosić plików. Co jest nie tak?
Sprawdź kolejno trzy elementy. Po pierwsze, sam punkt montowania mediów: skrypt inicjujący zmienia właściciela tylko dla /app, /config oraz /defaults, więc /data lub /downloads zachowują uprawnienia z hosta. Po drugie, współdzielona grupa: jeśli klient pobierający i Sonarr działają pod różnymi GID, żaden z nich nie może modyfikować plików drugiego, dlatego przypisz każdemu kontenerowi w stosie ten sam PGID. Po trzecie, umask: domyślna wartość UMASK=022 w obrazie zapisuje pliki jako 0644 bez bitu zapisu dla grupy, co całkowicie niweluje sens współdzielonej grupy. Ustaw UMASK=002 i nadaj bit setgid dla katalogów za pomocą chmod 2775, aby nowe pliki dziedziczyły grupę.