Qué hacen PUID y PGID en Docker Compose
PUID y PGID no son ajustes de Docker: son una convención de linuxserver.io. Descubre por qué los bind mounts crean archivos como 911:911 y cómo corregirlo.
Qué son realmente PUID y PGID
PUID y PGID son dos variables de entorno que algunas imágenes de contenedor leen al iniciarse. Docker no las consulta directamente. Son una convención que utilizan las imágenes de linuxserver.io y algunas otras. Por eso, una imagen que no esté preparada para leerlas las ignora silenciosamente.
Dentro de una imagen de linuxserver.io existe un usuario llamado abc, creado durante la compilación con UID (ID de usuario) 911 y GID (ID de grupo) 911. El contenedor se inicia como root, ejecuta sus scripts de inicialización y uno de ellos cambia el ID de ese usuario antes de que ocurra cualquier otra cosa:
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abcLa opción -o permite usar un ID que ya esté en uso en otro lugar. Después, el proceso de inicialización elimina los privilegios y ejecuta la aplicación como abc. Por tanto, PUID=1000 nunca llega a Docker. La variable cambia el ID de un usuario dentro del contenedor antes de que se inicie la aplicación. Esto hace que todos los archivos que escriba la aplicación aparezcan en el disco con el propietario 1000. Si deja PUID sin definir, abc conserva el ID 911. Por eso un bind mount sin configurar se llena de archivos cuyo propietario es 911:911.
Obtenga sus dos números con id
Ejecute esto en el host, con el usuario propietario de los directorios de datos:
iduid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)uid es su PUID y gid es su PGID. Para un script, id -u y id -g muestran únicamente los números. En la mayoría de las imágenes VPS recién instaladas, la primera cuenta de usuario es 1000:1000, pero no debe darlo por hecho. Un servidor reconstruido o una segunda cuenta añadida posteriormente puede tener 1001 o un valor superior. Un número incorrecto aquí es la causa completa del problema. Si los servicios se ejecutan con una cuenta de servicio dedicada en lugar de su propio usuario de inicio de sesión, ejecute id thatuser y obtenga los números de ahí.
Por qué sus archivos aparecen como 911:911
ls -l muestra un ID numérico en lugar de un nombre cuando ninguna cuenta del host coincide con ese ID. Ningún elemento del servidor tiene el UID 911, por lo que no hay ningún nombre que mostrar. Use ls -ln para mostrar siempre los números y eliminar la ambigüedad:
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.xmlEl resultado indica que el contenedor se ejecutó con los valores predeterminados integrados. Confírmelo desde el interior del contenedor en lugar de hacer suposiciones:
docker exec sonarr id abc
docker compose logs sonarr | head -n 25El proceso de inicialización de linuxserver muestra el resultado en el registro de inicio, en dos líneas:
User UID: 911
User GID: 911Si esas líneas muestran 911 después de establecer PUID=1000 en el archivo Compose, la variable nunca llegó al contenedor. La causa habitual es que editó docker-compose.yml y después ejecutó docker compose restart, que reutiliza el contenedor existente con su entorno original. Los cambios de entorno requieren docker compose up -d, que vuelve a crear el contenedor.
Por qué no puede eliminar un archivo escrito por el contenedor
El kernel compara números, nunca nombres. Su shell se ejecuta con el UID 1000. El archivo pertenece al UID 911. El directorio que lo contiene es drwxr-xr-x y también pertenece a 911, por lo que el grupo y los demás usuarios tienen permisos de lectura y ejecución, pero no de escritura. Para eliminar un archivo se necesita permiso de escritura sobre su directorio, no sobre el archivo. Por eso obtiene este resultado aunque el propio archivo parezca inofensivo:
rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission deniedUn contenedor que escribe encuentra el mismo problema desde el otro lado. Si el directorio del host pertenece a su usuario y tiene el modo 755, pero la aplicación se ejecuta con el UID 911, su primera escritura falla con Permission denied y la aplicación lo informa con su propia terminología. En una aplicación .NET como Sonarr o Radarr, esto aparece como UnauthorizedAccessException: Access to the path '/data/downloads' is denied. La cadena de permisos situada delante del archivo indica cuál de los tres conjuntos de permisos se está aplicando a su usuario. Leer correctamente drwxr-xr-x es lo que convierte ese error, que parece misterioso, en algo evidente.
Este es específicamente un problema de un bind mount. Cuando Docker crea un volumen con nombre vacío y lo monta sobre una ruta que existe en la imagen, copia el contenido de esa ruta al volumen, incluidos el propietario y los bits de permisos. Así, la aplicación encuentra un directorio que ya le pertenece. Un bind mount no recibe ese tratamiento: Docker monta el directorio del host exactamente como está. Esta diferencia es una de las razones prácticas para saber cuándo conviene usar un bind mount en lugar de un volumen con nombre y cuándo no.
Corregir un directorio que ya tiene permisos incorrectos
Configurar PUID y PGID cambia el comportamiento de la aplicación a partir de ese momento. No corrige de forma retroactiva los archivos que ya están en el disco. Detenga el stack, corrija manualmente el propietario y vuelva a iniciarlo:
docker compose down
sudo chown -R 1000:1000 /srv/appdata/sonarr
docker compose up -dUse sudo chown -R "$(id -u):$(id -g)" /srv/appdata/sonarr si prefiere no escribir los números. Hágalo con el contenedor detenido, porque una aplicación activa que esté escribiendo durante un chown recursivo puede dejar el árbol de directorios corregido sólo parcialmente y provocar una segunda serie de errores difíciles de interpretar.
Qué no solucionan PUID y PGID
Esta es la parte que suele afectar a quienes han hecho todo correctamente. El script de inicialización de linuxserver ejecuta chown únicamente sobre tres rutas al iniciar: /app, /config y /defaults. Los montajes de medios no están en esa lista. /data, /downloads y /tv se entregan a la aplicación sin modificaciones. Por tanto, si el lado del host de esos montajes tiene una propiedad que impide escribir al usuario del contenedor, el contenedor se inicia correctamente, muestra el UID correcto en su banner y después falla en la primera importación.
Ese es el comportamiento correcto. Ejecutar un chown recursivo sobre una biblioteca de medios de doce terabytes cada vez que se inicia el contenedor sería un desastre. Esto significa que las rutas de medios son responsabilidad suya y que en esos montajes es donde realmente suelen producirse los problemas de permisos.
Tres formas de controlar el usuario y cuándo aplicar cada una
Variables de entorno PUID y PGID
Esto sólo funciona en imágenes cuyo entrypoint las lee. Es una opción habitual porque el contenedor sigue iniciándose como root, realiza su propia configuración, corrige /config y sólo después reduce privilegios. Docker Mods y los scripts de inicialización personalizados siguen funcionando. El coste es que depende de una convención y no de una función de la plataforma, y los nombres de las variables no son estándar entre proyectos.
La clave user: en Compose
Esta es una función real de Docker y funciona con cualquier imagen, porque el runtime del contenedor la aplica antes de que se ejecute el código de la imagen:
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"El proceso nunca se ejecuta como root, ni siquiera durante un instante, lo que supone una mejora de seguridad real. También impide cualquier operación del entrypoint que necesite root. En las imágenes de linuxserver, el proyecto admite esta opción de forma razonable y sólo para las imágenes que ha probado. Las limitaciones son específicas: PUID y PGID dejan de tener efecto, Docker Mods no se ejecutará, los servicios personalizados no se ejecutarán y usted será responsable de los permisos de todos los volúmenes montados. El patrón documentado combina la opción con un /run escribible:
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=trueUn efecto secundario meramente visual puede sorprender. Un user: numérico no tiene ninguna entrada correspondiente en el /etc/passwd del contenedor, por lo que las herramientas internas muestran whoami: cannot find name for user ID 1000. El ID es válido y el acceso a los archivos funciona con normalidad. Sólo falla la búsqueda del nombre.
Docker rootless
Docker rootless ejecuta el daemon con su propio usuario sin privilegios, por lo que ningún proceso del equipo se ejecuta como root real. Esto cambia por completo la correspondencia de propietarios. El UID 0 del contenedor se asigna al UID del usuario del host que ejecuta Docker rootless. El UID n del contenedor, para cualquier n de 1 o más, se asigna a subuid + (n - 1), donde subuid es la base del rango asignado a usted en /etc/subuid y /etc/subgid. Docker espera que allí haya al menos 65,536 IDs subordinados.
Revise esa correspondencia, porque invierte la recomendación habitual. Con Docker rootless, un contenedor que escribe como root crea archivos que le pertenecen a usted. Un contenedor que escribe como UID 1000 crea archivos propiedad de un ID subordinado cercano a 100999, que su shell no puede modificar. Por tanto, el valor PUID correcto en un daemon rootful es incorrecto aquí. Ambos mecanismos resuelven el mismo problema en capas diferentes. Combinarlos sin comprobar su efecto es la forma de acabar con un directorio que necesita sudo para eliminar. Si va a usar Docker rootless, compruebe en su propio servidor la propiedad de un archivo creado antes de migrar una biblioteca.
Para la mayoría de las pilas autoalojadas en un único VPS, usar PUID y PGID con un daemon rootful es la opción pragmática, porque es el modelo para el que se han creado y documentado las imágenes. Use user: cuando el README de la imagen indique que se ha probado con esa opción o cuando ejecute una imagen oficial del proyecto original que no admita PUID.
El caso del stack multimedia: un grupo compartido entre contenedores
Un stack multimedia de arr con Sonarr, Radarr y un cliente de descargas es donde esto deja de ser teoría. El cliente de descargas escribe el archivo terminado en /data/downloads. Sonarr crea un hardlink o mueve ese archivo a /data/media. Para que el hardlink funcione, los dos contenedores necesitan acceso de escritura al mismo árbol. Si el cliente de descargas se ejecuta como 1000 y Sonarr como 1001, uno de ellos será propietario de archivos que el otro sólo podrá leer.
La solución es un grupo compartido que todos los contenedores del stack usen como su 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 {} +El 2 inicial de 2775 es el bit setgid. En un directorio, indica que cada archivo y subdirectorio nuevo creado dentro hereda el grupo media en lugar del grupo primario propio del usuario que lo crea. Así, la configuración se mantiene para las nuevas descargas sin tener que volver a ejecutar chown. Cierre la sesión y vuelva a iniciarla, o ejecute newgrp media, antes de comprobar sus propios permisos. Un grupo añadido con usermod -aG no aparece en una sesión de shell ya abierta.
Dentro del contenedor, groupmod -o -g 13000 abc cambia el GID del grupo abc a 13000. De este modo, abc escribe con el mismo GID que el grupo media del host. Cada contenedor del stack conserva su propio PUID y comparte ese único PGID.
A continuación, configure UMASK=002 en todos los contenedores linuxserver del stack. Este es el paso que suele omitirse. El valor predeterminado de estas imágenes es UMASK=022. Este valor elimina el bit de escritura del grupo en cada archivo nuevo, por lo que los archivos se crean como 0644 y el uso compartido que acaba de configurar no funciona. 002 crea archivos 0664 y directorios 0775, y el grupo puede escribir:
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-stoppedEstos dos valores deben estar en un archivo .env junto al archivo de Compose, para que todo el stack use una sola definición:
PUID=1000
PGID=13000Compose lee automáticamente ese archivo para la sustitución de variables de estilo ${PUID}. Es el mismo mecanismo que se usa para las credenciales. También se aplica aquí la práctica de mantener los valores fuera de docker-compose.yml y guardarlos en un archivo .env, con la diferencia de que estos dos números no son secretos.
Verifique todo el proceso en lugar de confiar en la configuración. Escriba un archivo desde un contenedor y léalo desde el host:
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtestUn resultado correcto muestra su PUID como propietario, 13000 como grupo y -rw-rw-r-- como permisos. Si los permisos del grupo muestran 1000, falta el bit setgid en ese directorio. Si los permisos muestran -rw-r--r--, la variable UMASK no se aplicó. Compruebe que volvió a crear el contenedor en lugar de limitarse a reiniciarlo. Cuando termine, elimine el archivo de prueba con rm /srv/media/downloads/permtest.
Qué imágenes usan cada variable
Las imágenes de linuxserver.io usan PUID, PGID y UMASK. Paperless-ngx utiliza otros nombres para la misma idea: USERMAP_UID y USERMAP_GID, ambos con el valor predeterminado 1000. Su documentación indica que debe obtenerlos de id -u y id -g. Muchas imágenes oficiales del proyecto original, incluidas las imágenes habituales de bases de datos y servidores web, incorporan un usuario fijo y esperan que use user: o que no lo modifique.
Por tanto, revise el README de cada imagen antes de copiar un bloque de variables de entorno entre proyectos. Docker pasa cualquier variable de entorno que establezca a cualquier contenedor, aunque ningún componente interno la lea. Un PUID que ningún componente consume no produce ningún error, advertencia ni efecto. El contenedor se ejecuta con el usuario que se haya definido al final de su propio Dockerfile. Puede determinarlo mediante la propiedad de los archivos que escribe.
FAQ
¿Por qué los archivos de Docker son propiedad de 911:911?
911 es el UID y el GID del usuario abc integrado en las imágenes de linuxserver.io. Esto indica que el contenedor se inició sin definir PUID y PGID, por lo que su script de inicialización mantuvo los valores predeterminados integrados. ls -l muestra los números sin traducir porque ninguna cuenta del host tiene el ID 911 y, por tanto, no hay ningún nombre que mostrar. Establezca PUID y PGID con la salida de id, vuelva a crear el contenedor con docker compose up -d y corrija los archivos existentes con sudo chown -R 1000:1000 en el directorio afectado.
¿Funcionan PUID y PGID en todas las imágenes de Docker?
No. No son una función de Docker y Docker nunca los lee. Sólo funcionan en imágenes cuyo propio entrypoint los lee y ejecuta usermod y groupmod antes de iniciar la aplicación. Este es el caso de la familia linuxserver.io y de algunos proyectos que copiaron este patrón. Otros proyectos usan nombres diferentes, como USERMAP_UID y USERMAP_GID en paperless-ngx. En una imagen que no lee ninguna de estas variables, se aceptan y se ignoran sin mostrar ninguna advertencia.
¿Debo usar PUID y PGID o la clave user: en Docker Compose?
Use PUID y PGID cuando la imagen los admita, porque el entrypoint todavía se ejecuta como root el tiempo suficiente para corregir /config e iniciar correctamente sus propios servicios. Use user: cuando la imagen no admita PUID o cuando el README de la imagen indique que se ha probado su ejecución sin root. En una imagen de linuxserver, establecer user: deja PUID y PGID sin efecto, impide que se ejecuten Docker Mods y los servicios personalizados, y hace que los permisos de cada volumen montado sean responsabilidad suya.
Sonarr tiene el PUID correcto, pero todavía no puede mover archivos. ¿Cuál es el problema?
Compruebe tres aspectos, en este orden. Primero, el propio montaje de medios: el proceso de inicialización sólo ejecuta chown sobre /app, /config y /defaults, por lo que /data o /downloads conserva la propiedad que tenga en el host. Segundo, el grupo compartido: si el cliente de descargas y Sonarr se ejecutan con GID diferentes, ninguno puede modificar los archivos del otro. Asigne el mismo PGID a todos los contenedores de la pila. Tercero, la umask: el valor predeterminado de la imagen, UMASK=022, crea archivos como 0644 sin permisos de escritura para el grupo, lo que inutiliza por completo un grupo compartido. Establezca UMASK=002 y active el bit setgid en los directorios con chmod 2775 para que los archivos nuevos hereden el grupo.