Что такое PUID и PGID в Docker Compose
Переменные PUID и PGID не являются настройками Docker. Это соглашение образов linuxserver.io для управления правами доступа. Узнайте, как избежать проблем с владельцем файлов.
Что такое PUID и PGID на самом деле
PUID и PGID — это две переменные окружения, которые считываются некоторыми образами контейнеров при запуске. Сам Docker их не использует. Это соглашение, применяемое в образах linuxserver.io и ряде других; если образ не был спроектирован для их считывания, он просто проигнорирует эти переменные.
Внутри образа linuxserver.io существует пользователь abc, созданный на этапе сборки с UID (идентификатором пользователя) 911 и GID (идентификатором группы) 911. Контейнер запускается от имени root, выполняет свои скрипты инициализации, и один из этих скриптов переназначает идентификаторы этого пользователя до выполнения любых других действий:
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abcФлаг -o позволяет использовать идентификатор, который уже занят где-то еще. После этого процесс инициализации сбрасывает привилегии и запускает приложение от имени abc. Таким образом, PUID=1000 никогда не передается в Docker. Переменная переназначает пользователя внутри контейнера до запуска приложения, что означает, что каждый файл, записанный приложением, будет принадлежать пользователю с ID 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 выводит числовой идентификатор вместо имени, если в системе нет учетной записи с таким ID. На вашем сервере нет пользователя с 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Скрипт инициализации 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, а приложение запущено от имени 911, первая попытка записи завершится ошибкой Permission denied, и приложение сообщит об этом в своих логах. В .NET-приложениях, таких как Sonarr или Radarr, это выглядит как UnauthorizedAccessException: Access to the path '/data/downloads' is denied. Строка прав доступа перед именем файла показывает, какой из трех наборов прав применяется к вам в данный момент, и правильное чтение drwxr-xr-x превращает эту загадочную ошибку в очевидную.
Это специфическая проблема bind mount. Когда Docker создает пустой именованный том и монтирует его поверх пути, существующего в образе, он копирует содержимое этого пути в том, включая владельца и биты прав доступа, поэтому приложение получает директорию, которой оно уже владеет. Bind mount не проходит через такую процедуру: Docker монтирует директорию хоста в точности в том виде, в котором она есть. Эта разница — одна из практических причин знать, когда bind mount лучше именованного тома, а когда нет.
Исправление уже некорректных прав доступа к директории
Установка PUID и PGID меняет поведение приложения только для будущих операций. Это не исправляет права доступа для файлов, которые уже находятся на диске. Остановите стек, самостоятельно исправьте владельца файлов, а затем запустите стек снова:
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
Это тот момент, на котором спотыкаются пользователи, сделавшие всё правильно. При запуске скрипт инициализации linuxserver выполняет chown ровно для трёх путей: /app, /config и /defaults. Ваши точки монтирования медиафайлов в этот список не входят. /data, /downloads и /tv передаются приложению без изменений. Если права доступа на хост-системе для этих путей не позволяют пользователю контейнера выполнять запись, контейнер запустится без ошибок, выведет в логах правильный UID, но завершится неудачей при первой же попытке импорта.
Это корректное поведение. Рекурсивный chown для медиабиблиотеки объемом двенадцать терабайт при каждом запуске контейнера привел бы к катастрофе. Это означает, что настройка прав доступа к медиадиректориям — ваша задача, и именно в этих точках монтирования чаще всего возникают проблемы с доступом.
Три способа управления пользователем и области их применения
Переменные окружения PUID и PGID
Этот метод работает только с образами, точка входа (entrypoint) которых считывает данные переменные. Он популярен, так как контейнер всё равно запускается от имени root, выполняет собственную настройку, исправляет /config и только после этого понижает привилегии. Docker Mods и пользовательские скрипты инициализации продолжают работать. Минус в том, что вы полагаетесь на соглашение, а не на встроенную функцию платформы, к тому же названия переменных различаются в разных проектах.
Ключ user: в Compose
Это полноценная функция Docker, работающая с любым образом, так как среда выполнения контейнера применяет её до запуска кода самого образа:
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"Процесс никогда не запускается от имени root, даже на мгновение, что даёт реальное преимущество в безопасности. Это также нарушает работу любых компонентов точки входа, требующих прав root. В образах linuxserver проект поддерживает этот метод на основе принципа «разумных усилий» и только для протестированных образов. Есть важные нюансы: PUID и PGID перестают действовать, Docker Mods не запускаются, пользовательские сервисы не работают, а вы несёте полную ответственность за права доступа на всех смонтированных томах. Документированный шаблон предполагает использование этого флага вместе с доступным для записи /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. Идентификатор является корректным, и доступ к файлам работает в штатном режиме. Ошибка возникает только при поиске имени пользователя.
Rootless Docker
Rootless Docker запускает сам демон от имени вашего непривилегированного пользователя, поэтому ничто на хосте не работает от имени реального root. Это полностью меняет арифметику владения файлами. UID 0 в контейнере отображается на UID пользователя хоста, который запустил rootless Docker, а UID контейнера n для любого n от 1 и выше отображается на subuid + (n - 1), где subuid — это база диапазона, выделенного вам в /etc/subuid и /etc/subgid. Docker ожидает наличия там как минимум 65536 подчинённых идентификаторов.
Внимательно изучите это отображение, так как оно инвертирует привычные рекомендации. В rootless Docker контейнер, записывающий файлы от имени root, создаёт файлы, владельцем которых являетесь вы. Контейнер, записывающий файлы от имени UID 1000, создаёт файлы, принадлежащие подчинённому идентификатору в районе 100999, к которым ваша оболочка не имеет доступа. Таким образом, значение PUID, корректное для обычного демона, здесь будет неверным. Эти два механизма решают одну и ту же задачу на разных уровнях, и их совместное использование без проверки приводит к тому, что для удаления директории вам потребуется sudo. Если вы переходите на rootless, проверьте владельца одного записанного файла на своём сервере, прежде чем переносить в него библиотеку данных.
Для большинства self-hosted стеков на одном VPS использование PUID и PGID на обычном демоне является прагматичным выбором, так как именно под это построены и задокументированы образы. Используйте user:, когда в README образа указано, что он протестирован для этого режима, или когда вы запускаете официальный upstream-образ, в котором вообще нет поддержки PUID.
Сценарий медиа-стека: общая группа для нескольких контейнеров
Медиа-стек arr с Sonarr, Radarr и клиентом для загрузки — это пример, где теория переходит в практику. Клиент для загрузки записывает готовый файл в /data/downloads. Затем Sonarr создает жесткую ссылку или перемещает этот файл в /data/media. Чтобы жесткая ссылка сработала, оба контейнера должны иметь права на запись в одну и ту же директорию. Если клиент для загрузки работает от имени пользователя 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, не отображается в уже открытой сессии оболочки.
Внутри контейнера 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=13000Compose автоматически считывает этот файл для подстановки переменных в стиле ${PUID} — это тот же механизм, который используется для учетных данных. Правила выноса значений из docker-compose.yml в файл .env применимы и здесь, с той разницей, что эти два числа не являются секретными.
Проверьте конфигурацию целиком, вместо того чтобы полагаться на настройки. Создайте файл из одного контейнера и прочитайте его на хосте:
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtestКорректный результат показывает ваш PUID в качестве владельца, 13000 в качестве группы и -rw-rw-r-- в качестве прав доступа. Если группа имеет права 1000, значит, на директории отсутствует бит setgid. Если права отображаются как -rw-r--r--, значит, переменная UMASK не применилась; убедитесь, что вы пересоздали контейнер, а не просто перезапустили его. Удалите тестовый файл с помощью rm /srv/media/downloads/permtest после завершения проверки.
Какие образы используют какие переменные
Образы linuxserver.io используют PUID, PGID и UMASK. В Paperless-ngx для тех же целей применяются другие имена: USERMAP_UID и USERMAP_GID, значения которых по умолчанию равны 1000; документация рекомендует считывать их из id -u и id -g. Многие официальные образы от разработчиков ПО, включая стандартные образы баз данных и веб-серверов, поставляются с фиксированным встроенным пользователем и ожидают, что вы будете использовать user: или оставите его без изменений.
Поэтому проверяйте README каждого образа перед тем, как копировать блок переменных окружения между проектами. Docker передает любую заданную вами переменную окружения в любой контейнер, независимо от того, считывает ли её что-либо внутри, а PUID, которую никто не использует, не вызывает ни ошибок, ни предупреждений, ни каких-либо последствий. Контейнер запускается от имени того пользователя, который был указан в конце его Dockerfile, а узнать это можно по владельцу файлов, которые он создает.
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-образе?
Нет. Это не функция 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 и пользовательских скриптов, а ответственность за права доступа ко всем смонтированным томам ложится на вас.
У Sonarr верный PUID, но он всё равно не может перемещать файлы. В чем проблема?
Проверьте три вещи по порядку. Во-первых, сам том с медиафайлами: скрипт инициализации меняет владельца только для /app, /config и /defaults, поэтому /data или /downloads сохраняют те права, которые были установлены на хосте. Во-вторых, общая группа: если клиент загрузки и Sonarr работают под разными GID, они не смогут изменять файлы друг друга, поэтому назначьте всем контейнерам в стеке одинаковый PGID. В-третьих, umask: значение по умолчанию UMASK=022 создает файлы с правами 0644 без бита записи для группы, что полностью нивелирует смысл общей группы. Установите UMASK=002 и примените setgid-бит к каталогам с помощью chmod 2775, чтобы новые файлы наследовали группу.