Qué hacen PUID y PGID en Docker Compose
PUID y PGID no son ajustes de Docker: son una convención de linuxserver.io. Explica por qué los bind mounts quedan 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 durante el arranque. Docker no las consulta. Son una convención que utilizan las imágenes de linuxserver.io y algunas otras. Por tanto, una imagen que no esté preparada para leerlas las ignora sin mostrar ningún mensaje.
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 número de ese usuario antes de iniciar la aplicación:
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 script de inicialización elimina privilegios y ejecuta la aplicación como abc. Docker pasa PUID=1000 al contenedor como cualquier otra variable de entorno y nunca actúa sobre ella. La variable cambia el ID de un usuario dentro del contenedor antes de iniciar la aplicación. Esto significa que, con un daemon rootful estándar sin reasignación de espacios de nombres de usuario, los archivos nuevos normalmente se crean en el disco con el propietario 1000. Escribir en un archivo existente con permisos de escritura no cambia su propietario. Rootless Docker cambia ese número, como se explica más adelante. Si no define PUID, abc conserva el valor 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 usarlo en un script, id -u y id -g imprimen sólo los números. En la mayoría de las imágenes VPS recién creadas, la primera cuenta de usuario es 1000:1000, pero no debe darlo por supuesto. La asignación depende de las cuentas y de la configuración existentes: un servidor reinstalado puede volver a asignar 1000, mientras que una cuenta creada posteriormente obtiene un ID disponible más alto. Si sus 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é tus 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. Si ninguna cuenta del host coincide con el UID 911, no hay ningún nombre que mostrar. Usa 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.xmlEse resultado coincide con los valores predeterminados integrados en el contenedor, pero los archivos existentes por sí solos no demuestran con qué usuario se ejecuta ahora la aplicación. El config.xml de ese listado también es el archivo que contiene la configuración de autenticación. Esto importa la primera vez que abres la interfaz web y descubres que Sonarr y Radarr se distribuyen sin nombre de usuario ni contraseña predeterminados. Confírmalo desde dentro del contenedor en lugar de hacer suposiciones:
docker exec sonarr id abc
docker compose logs sonarr | head -n 25La inicialización de linuxserver muestra el resultado en el registro de arranque en dos líneas:
User UID: 911
User GID: 911Si esas líneas muestran 911 después de establecer PUID=1000 en tu archivo Compose, el contenedor en ejecución no está usando el valor que estableciste. La causa habitual es que editaste docker-compose.yml y después ejecutaste 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. La otra causa es intencionada: cuando una imagen se ejecuta en modo de solo lectura o sin root, la inicialización omite por completo la renumeración aunque la variable esté presente en el contenedor. En modo de solo lectura, el banner muestra el ID sin cambios de abc; en modo sin root, muestra el propietario de /run.
Por qué no puede eliminar un archivo que escribió 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 aparece 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 con permisos 755 y la aplicación se ejecuta con el UID 911, su primera escritura falla con Permission denied y la aplicación lo informa con sus propios términos. 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 aplica a su usuario. Leer correctamente drwxr-xr-x permite convertir ese error, que parece misterioso, en un problema evidente.
Este problema es específico de los bind mounts. Cuando Docker crea un volumen con nombre vacío y lo monta sobre una ruta que existe en la imagen, copia en el volumen el contenido de esa ruta, 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 uno de los motivos prácticos 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
Definir PUID y PGID cambia el comportamiento de la aplicación a partir de ese momento. No corrige de forma retroactiva todos los archivos que ya existen en el disco, aunque el script de inicialización corrige la propiedad en rutas específicas que se describen a continuación. Detenga el stack, corrija la propiedad manualmente 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 que esté escribiendo mientras se ejecuta 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 afecta a quienes han hecho todo correctamente. El script de configuración de usuarios de la imagen base de linuxserver cambia el propietario de 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 un propietario al que el usuario del contenedor no puede escribir, el contenedor se inicia correctamente, muestra el UID correcto en su banner y después falla en la primera importación.
Ese comportamiento es correcto. Ejecutar un chown recursivo sobre una biblioteca multimedia de doce terabytes cada vez que se inicia el contenedor sería desastroso. Esto significa que los directorios de medios son responsabilidad suya. También son los montajes donde realmente suelen producirse los problemas de permisos. Este tipo de fallo aparece de forma silenciosa en el registro de la aplicación horas después de que el contenedor parezca estar funcionando correctamente. Por eso, una prueba periódica de escritura integrada con ntfy en su propio VPS, con alertas enviadas al teléfono es una forma sencilla de detectarlo antes de que una semana sin episodios lo ponga de manifiesto.
Tres formas de controlar el usuario y cuándo se aplica cada una
Variables de entorno PUID y PGID
Esto sólo funciona con imágenes cuyo entrypoint las lee. Es una opción popular porque el contenedor sigue iniciándose como root, realiza su propia configuración, corrige /config y sólo después abandona los 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 ejecutar el código propio 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 rompe cualquier elemento del entrypoint que necesitara 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 ejecuta, los servicios personalizados no se ejecutan y usted pasa a 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 que no tenga una entrada coincidente en el /etc/passwd del contenedor hace que las herramientas internas informen 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 resolución del nombre.
Docker rootless
Docker rootless ejecuta el propio daemon con su usuario sin privilegios. Por tanto, el daemon y los contenedores se ejecutan sin privilegios de root en el host. Esto cambia por completo el cálculo de propietarios. El UID 0 del contenedor se asigna al UID del host correspondiente al usuario que ejecuta Docker rootless. El UID n del contenedor, para cualquier n de 1 o superior, se asigna a subuid + (n - 1), donde subuid es la base del rango asignado en /etc/subuid. Los ID de grupo siguen la misma regla mediante /etc/subgid. Docker requiere al menos 65,536 ID subordinados en esa ubicación.
Vuelva a leer ese mapeo, porque invierte la recomendación habitual. Con Docker rootless, un contenedor que crea un archivo como root normalmente produce un archivo cuyo propietario es usted. Un contenedor que crea un archivo con UID 1000 normalmente produce un archivo cuyo propietario es un ID subordinado, como 100999 cuando el rango comienza en 100000. El acceso desde su shell depende de los permisos y las ACL. La eliminación normalmente depende del directorio contenedor, por lo que puede eliminar ese archivo desde un directorio que controle. Por tanto, el valor de PUID correcto en un daemon rootful es incorrecto aquí. Ambos mecanismos cambian el propietario en capas diferentes. Combinarlos sin comprobar el resultado puede impedirle escribir en los datos montados. Si usa Docker rootless, pruebe el propietario de un archivo creado en su propio servidor antes de migrar una biblioteca.
Para la mayoría de las pilas autohospedadas en un único VPS, PUID y PGID con un daemon rootful son la opción práctica, porque es la configuración para la que se crean y documentan las imágenes. Use user: cuando el README de la imagen indique que está probado para ella o cuando ejecute una imagen oficial del proyecto original que no admita PUID. Un espacio de trabajo documental como una instancia autohospedada de AFFiNE en un VPS pertenece al segundo caso, porque ninguno de sus contenedores lee PUID y el propietario de su directorio de base de datos y de los archivos cargados lo establece el runtime, no ningún elemento del bloque de entorno. Lo mismo ocurre con una mesa de soporte autohospedada de Chatwoot, donde el contenedor de Rails y su worker de Sidekiq escriben en un único directorio de cargas y ninguno lee PUID. Por tanto, ese directorio debe coincidir con el usuario con el que la imagen ya se ejecuta. Esto tampoco cambia en una pila más reciente. Por ello, dar a cada persona del equipo su propio agente OneCLI aislado deja los directorios de trabajo individuales y el directorio de datos de Postgres con el propietario del usuario con el que ya se ejecuta cada imagen, lo que convierte la situación en un problema de user: y chown, no de PUID. Si coloca una única API autohospedada delante de Codex, Claude Code y Hermes, su entrypoint gestiona varias identidades: el contenedor debe iniciarse como root, la consola y el gateway se ejecutan con un usuario sin privilegios y cada sesión del agente se ejecuta con su propio usuario. No añada user: a ese despliegue; su entrypoint gestiona el propietario del volumen de datos.
El caso de la pila multimedia: un grupo compartido entre contenedores
Una pila multimedia de arr con Sonarr, Radarr y un cliente de descargas es donde esto deja de ser teoría. El cliente de descargas escribe un archivo terminado en /data/downloads. Después, Sonarr crea un enlace duro o mueve ese archivo a /data/media. Para que el enlace duro funcione, los dos contenedores necesitan acceso de escritura al mismo árbol. Si el cliente de descargas se ejecuta como 1000 y Sonarr como 1001 sin permisos de escritura compartidos mediante un grupo, uno de ellos será propietario de archivos que el otro sólo podrá leer.
La solución es usar un grupo compartido que todos los contenedores de la pila utilicen como 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 creador. Así, la configuración se mantiene al descargar archivos nuevos sin tener que volver a ejecutar chown. Cierre la sesión y vuelva a iniciarla, o ejecute newgrp media, antes de comprobar su propio acceso. 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 grupo abc a 13000, de modo que abc escribe con el mismo GID que el grupo media del host. Cada contenedor de linuxserver que accede a este árbol conserva su propio PUID y comparte ese PGID. Esto incluye los contenedores posteriores de la cadena que sólo leen la biblioteca terminada, como el propio Jellyfin. Un frontend como Halcyon, que sirve esa biblioteca como una videoteca de los años 90 que se puede recorrer, se conecta a Jellyfin mediante HTTP usando HALCYON_JELLYFIN_URL. Su implementación estándar no necesita el PGID multimedia compartido ni un montaje del directorio multimedia.
Después, configure UMASK=002 en cada contenedor de linuxserver de la pila. Este es el paso que suele omitirse. Si no se establece, la aplicación conserva su umask heredada, salvo que otro componente la cambie, y el valor de ejemplo que muestran los README de la imagen es UMASK=022. Con una umask efectiva de 022, una aplicación que solicita 0666 para un archivo nuevo obtiene 0644 cuando no existe una ACL predeterminada. El resto del grupo todavía puede leerlo, pero no modificarlo, lo que anula gran parte del uso compartido que acaba de configurar. Con 002 y sin una ACL predeterminada, una aplicación que solicita los valores habituales 0666 para archivos y 0777 para directorios obtiene archivos 0664 y, dentro del árbol setgid, directorios 2775, y el grupo puede escribir. La umask sólo elimina bits. Por tanto, una aplicación que solicita 0600 todavía obtiene 0600. Un directorio que tiene una ACL predeterminada ignora completamente la umask. Añada la variable junto a PUID y PGID:
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 Compose, para que toda la pila lea una única definición:
PUID=1000
PGID=13000Compose lee ese archivo automáticamente para la sustitución de estilo ${PUID}, que es el mismo mecanismo que se utiliza para las credenciales. Las prácticas de mantener los valores fuera de docker-compose.yml y dentro de un archivo .env también se aplican aquí, 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 nuevo como el usuario de la aplicación, aplicando la umask configurada, y léalo desde el host. Use un nombre de archivo de prueba que no exista previamente:
docker exec --user abc sonarr sh -c 'umask "$UMASK" && 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 el grupo aparece como 1000, ejecute ls -ldn /srv/media/downloads. Falta el bit setgid (el s de drwxrwsr-x) en ese directorio, o el propio directorio todavía pertenece al grupo 1000 en lugar de media. Los comandos chown y chmod 2775 anteriores corrigen ambos problemas. Si los permisos aparecen como -rw-r--r--, compruebe el valor UMASK del contenedor y la ACL predeterminada del directorio. Vuelva a crear el contenedor si su entorno está desactualizado. Esta prueba comprueba la creación como abc con la máscara configurada. El modo de creación propio de la aplicación y su umask efectiva siguen determinando los permisos de sus archivos. Cuando termine, elimine el archivo de prueba con rm /srv/media/downloads/permtest.
Qué imágenes utilizan cada variable
Las imágenes de linuxserver.io utilizan PUID, PGID y UMASK. Paperless-ngx usa nombres distintos para la misma idea: USERMAP_UID y USERMAP_GID, ambos con el valor predeterminado 1000, y su documentación indica que se deben leer de id -u y id -g. Los servidores de fotos muestran la misma diversidad: PhotoPrism tiene su propio par PHOTOPRISM_UID y PHOTOPRISM_GID, mientras que Immich no incluye un equivalente y deja el usuario del contenedor en manos de la clave user: de Docker. Por tanto, elegir entre PhotoPrism e Immich también determina cuál de estos mecanismos deberá mantener para la biblioteca más grande del equipo. Muchas imágenes oficiales de los proyectos originales, incluidas las imágenes habituales de bases de datos y servidores web, incluyen un usuario integrado fijo y esperan que utilice user: o que no lo modifique. Las implementaciones pequeñas de una sola aplicación plantean la misma cuestión. Por eso, cuando configure un rastreador de entrenamientos openGym autoalojado, compruebe con qué usuario se ejecuta realmente su contenedor antes de apuntarlo a un bind mount. El directorio que contiene su base de datos heredará esa configuración, independientemente de que establezca PUID. Un relé de acceso remoto pertenece a la misma categoría. Por eso, cuando ejecute su propio servidor de relé RustDesk, el par de claves Ed25519 que hbbs escribe durante el primer arranque aparecerá en su bind mount con la propiedad del usuario con el que termine esa imagen. Puede corregirlo después con un chown ejecutado en el host, o elegir la identidad desde el principio con la clave user:, porque esa imagen no establece ningún usuario propio, siempre que el directorio montado permita escribir al ID elegido. Lo mismo se aplica a la infraestructura que añada más adelante. Por tanto, colocar Authentik delante de sus aplicaciones para usar un único inicio de sesión implica ejecutar las imágenes oficiales de authentik y Postgres, que no leen PUID. La propiedad de sus volúmenes procede de los usuarios con los que se ejecutan sus procesos y de la configuración que realizan sus entrypoints. Authentik eliminó su dependencia de Redis en la versión 2025.10.
Compruebe el README de cada imagen antes de copiar un bloque de entorno entre proyectos. Docker pasa cualquier variable de entorno que establezca a cualquier contenedor, tanto si algún componente la lee como si no. Un PUID que ningún componente consume no produce ningún error, advertencia ni efecto. El contenedor se inicia con el usuario seleccionado por la configuración user: de Docker o por el Dockerfile. Después, su entrypoint puede reducir privilegios antes de iniciar la aplicación. Revise esto antes de añadir cualquier componente nuevo al equipo, incluido un stack de análisis de seguridad open-kritt autoalojado, cuyo archivo Compose indica si las imágenes respetan PUID o si la propiedad de los directorios montados está fijada por las propias imágenes.
FAQ
¿Por qué mis archivos de Docker pertenecen a 911:911?
911 es el UID y GID del usuario abc integrado en las imágenes de linuxserver.io. Que coincida con los valores predeterminados integrados es normal, pero los archivos pueden conservar esa propiedad después de cambiar PUID y PGID del contenedor. ls -l muestra los números sin procesar cuando ninguna cuenta del host coincide con esos ID, por lo que 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. Funcionan sólo 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 sigue ejecutándose como root el tiempo suficiente para corregir /config e iniciar correctamente sus propios servicios. Use user: cuando la imagen documente la compatibilidad con el usuario elegido. La falta de compatibilidad con PUID por sí sola no significa que el entrypoint funcione sin root. En una imagen de linuxserver, establecer user: hace que PUID y PGID no tengan efecto, impide que se ejecuten Docker Mods y servicios personalizados, y deja bajo su responsabilidad los permisos de todos los volúmenes montados.
Sonarr tiene el PUID correcto, pero aún no puede mover archivos. ¿Cuál es el problema?
Compruebe tres aspectos, en este orden. Primero, el montaje de medios: el script de configuración de usuarios de la imagen base 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: asigne a los contenedores que acceden al árbol de medios el mismo PGID y permisos de escritura para el grupo. Tercero, la umask: una máscara efectiva de 022 produce archivos 0644 cuando la aplicación solicita 0666 y no se aplica ninguna ACL predeterminada, por lo que los otros contenedores del grupo pueden leerlos, pero no modificarlos. Dejar UMASK sin definir no garantiza una máscara ni un modo de archivo concretos. Establezca UMASK=002 y active el bit setgid en los directorios con chmod 2775 para que los archivos nuevos hereden el grupo. Una aplicación que solicite 0600 seguirá creando archivos privados, y una ACL predeterminada cambia la forma en que se asignan los permisos durante la creación.