SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor

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 determinadas imágenes de contenedor leen durante el arranque. Docker no las consulta directamente. Son una convención utilizada por las imágenes de linuxserver.io y por algunas otras. Por eso, una imagen que no está diseñada 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}" abc

La opción -o permite usar un ID que ya está en uso en otro lugar. Después, el proceso 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 que se inicie la aplicación. En un daemon rootful estándar, esto significa que todos los archivos que escriba la aplicación quedarán en el disco con el propietario 1000. Rootless Docker cambia ese número, como se explica más adelante. Si deja PUID sin definir, 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, como el usuario propietario de los directorios de datos:

id
uid=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 los números sin texto adicional. En la mayoría de las imágenes de 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 usar 1001 o un número superior, y un número incorrecto aquí es la causa completa del problema. 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 tome los números de ahí.

Por qué los 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. En el servidor no existe ningún 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/sonarr
drwxr-xr-x 2 911 911 4096 Aug  7 09:12 Backups
-rw-r--r-- 1 911 911  512 Aug  7 09:12 config.xml

La salida indica que el contenedor se ejecutó con los valores predeterminados integrados. El config.xml que aparece en esa lista también es el archivo que contiene la configuración de autenticación. Esto es importante la primera vez que abre la interfaz web y descubre que Sonarr y Radarr se distribuyen sin nombre de usuario ni contraseña predeterminados. Confírmelo desde dentro del contenedor en lugar de suponerlo:

docker exec sonarr id abc
docker compose logs sonarr | head -n 25

El proceso de inicialización de linuxserver muestra el resultado en el registro de arranque, en dos líneas:

User UID:    911
User GID:    911

Si esas líneas muestran 911 después de establecer PUID=1000 en el archivo Compose, el contenedor en ejecución no está usando el valor configurado. 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. La otra causa es intencionada: cuando una imagen se ejecuta en modo de solo lectura o sin privilegios de root, el proceso de inicialización omite por completo la reasignación de números y muestra el ID sin cambios de abc, aunque la variable esté presente en el contenedor.

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 denied

Un 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

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 existen 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 -d

Use 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 en ejecución que esté escribiendo durante un chown recursivo puede dejar el árbol de directorios corregido sólo parcialmente y provocar una segunda ronda 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 inicio de linuxserver cambia el propietario exactamente de tres rutas al arrancar: /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 modificar, por lo que, 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 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 desastroso. Esto significa que los directorios de medios son responsabilidad suya y que esos son los montajes donde realmente suelen producirse los problemas de permisos. Como 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, una prueba periódica de escritura conectada a ntfy en su propio VPS, con alertas en el 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 en 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 elimina privilegios. Docker Mods y los scripts de inicialización personalizados siguen funcionando. El coste es que se confía en una convención y no en 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 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 parte del entrypoint que necesite root. En las imágenes de linuxserver, el proyecto ofrece compatibilidad con esto según sus posibilidades 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 cada volumen montado. 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=true

Un efecto secundario meramente visual sorprende a algunas personas. Un user: numérico que no tiene una entrada correspondiente en el /etc/passwd del contenedor hace que las herramientas internas muestren 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 lo que ningún proceso del equipo se ejecuta como root real. Esto cambia por completo el cálculo de la propiedad. El UID 0 del contenedor se asigna al UID del host correspondiente al usuario que ejecuta Docker rootless. El UID del contenedor n, para cualquier n de 1 o más, se asigna a subuid + (n - 1), donde subuid es la base del rango asignado en /etc/subuid y /etc/subgid. Docker espera que allí haya al menos 65,536 ID subordinados.

Vuelva a leer esa asignación, porque invierte la recomendación habitual. Con Docker rootless, un contenedor que escribe como root crea archivos de los que usted es propietario. 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 de PUID correcto en un daemon rootful es incorrecto aquí. Ambos mecanismos resuelven el mismo problema en capas diferentes. Combinarlos sin comprobar el resultado es la forma de acabar con un directorio que necesita sudo para eliminarlo. Si usa rootless, pruebe la propiedad 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 esa imagen está probada con esta opción, 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 único VPS pertenece a este último caso, porque ninguno de sus contenedores lee PUID y el runtime determina la propiedad de su directorio de base de datos y de los archivos cargados, no nada del bloque de entorno. Lo mismo ocurre con una mesa de ayuda 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. Así, dar a cada persona del equipo su propio agente OneCLI aislado deja los directorios de trabajo de cada persona y el directorio de datos de Postgres propiedad del usuario con el que ya se ejecuta cada imagen. Esto lo convierte en un problema de user: y chown, no de PUID. Si coloca una única API autohospedada delante de Codex, Claude Code y Hermes, heredará la misma configuración, porque esa imagen se ejecuta con su propio usuario integrado y el bind mount que contiene su base de datos y las claves almacenadas adopta la propiedad de ese usuario.

El caso de la pila multimedia: un grupo compartido entre contenedores

Una pila multimedia 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. 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, 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 de la pila usen 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 todos los archivos y subdirectorios nuevos creados dentro heredan el grupo media en lugar del grupo primario propio del usuario que los crea. Así, la configuración sigue funcionando con nuevas descargas 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 renumera el grupo abc como 13000. Así, abc escribe con el mismo GID que el grupo media del host. Cada contenedor de la pila conserva su propio PUID y comparte ese PGID. Esto incluye los contenedores que aparecen más adelante en la cadena y que sólo leen la biblioteca terminada, como el propio Jellyfin y las interfaces que otros añaden, como Halcyon, que ofrece esa biblioteca como un videoclub de los años 90 por el que se puede navegar.

Después, establezca UMASK=002 en todos los contenedores linuxserver de la pila. Este es el paso que suele omitirse. Si no se establece, las imágenes no aplican ningún umask propio. La aplicación conserva el umask heredado, y el valor de ejemplo que muestran los README de las imágenes es UMASK=022. Un umask 022 elimina el bit de escritura del grupo. Por tanto, los archivos se crean como 0644. El resto del grupo todavía puede leerlos, pero no modificarlos, lo que anula gran parte del uso compartido que acaba de configurar. Con 002, una aplicación que solicita el valor habitual 0666 para los archivos y 0777 para los directorios obtiene archivos 0664 y, dentro del árbol setgid, directorios 2775. El grupo puede escribir en ambos casos. Un umask sólo elimina bits. Por tanto, una aplicación que solicita 0600 todavía obtiene 0600. Además, un directorio con una ACL predeterminada ignora por completo el 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-stopped

Esos 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=13000

Compose lee ese archivo automáticamente para la sustitución de variables al estilo ${PUID}. Es el mismo mecanismo que se usa para las credenciales. Las prácticas descritas en mantener los valores fuera de docker-compose.yml y colocarlos en un archivo .env también se aplican aquí, con la diferencia de que estos dos números no son secretos.

Compruébelo de extremo a extremo 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/permtest

Un resultado correcto muestra su PUID como propietario, 13000 como grupo y -rw-rw-r-- como modo. 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 directorio todavía pertenece al grupo 1000 en lugar de media. Los comandos chown y chmod 2775 anteriores corrigen ambos problemas. Si el modo aparece como -rw-r--r--, la variable UMASK no se aplicó. Compruebe que recreó el contenedor en lugar de 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 nombres distintos para la misma idea: USERMAP_UID y USERMAP_GID, ambos con el valor predeterminado 1000, y su documentación indica que deben leerse de id -u y id -g. Los servidores de fotos muestran la misma variedad: 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 servidor. Muchas imágenes oficiales de los proyectos originales, incluidas las imágenes habituales de bases de datos y servidores web, incluyen un usuario fijo y esperan que use user: o que lo deje sin cambios. Las implementaciones pequeñas de una sola aplicación plantean la misma cuestión. Por eso, al configurar un rastreador de entrenamientos openGym autoalojado, conviene comprobar con qué usuario se ejecuta realmente su contenedor antes de asignarle un bind mount. El directorio que contiene su base de datos adopta esa configuración, establezca o no PUID. Un relay de acceso remoto pertenece a la misma categoría. Al ejecutar su propio servidor relay de RustDesk, el par de claves Ed25519 que hbbs escribe durante el primer arranque aparece en el bind mount con la propiedad del usuario con el que termine ejecutándose esa imagen. Puede corregirlo después con un chown ejecutado en el host, o elegir la identidad desde el principio mediante la clave user:. Esa imagen no establece ningún usuario propio, siempre que el directorio montado permita escribir al ID seleccionado. Lo mismo se aplica a la infraestructura que añada más adelante. Por ejemplo, poner Authentik delante de sus aplicaciones para usar un único inicio de sesión implica ejecutar las imágenes oficiales de server, Postgres y Redis. Ninguna lee PUID, y la propiedad de sus volúmenes procede del runtime, no de un entrypoint configurable.

Por tanto, compruebe 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, tanto si algo dentro del contenedor la lee como si no. Un PUID que ningún proceso consume no produce ningún error, advertencia ni efecto. El contenedor se ejecuta con el usuario definido al final de su propio Dockerfile, y puede determinarlo mediante la propiedad de los archivos que escribe. Haga esta comprobación antes de añadir cualquier componente al servidor, incluido un stack de análisis de seguridad open-kritt autoalojado. El archivo Compose indica si las imágenes admiten PUID o si la propiedad de los directorios montados queda 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. Si aparece, significa que el contenedor se inició sin PUID y PGID definidos, 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 sigue ejecutándose 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 funcionamiento sin root. En una imagen de linuxserver, establecer user: deja PUID y PGID inactivos, impide que se ejecuten Docker Mods y servicios personalizados, y hace que los permisos de todos los volúmenes montados sean responsabilidad suya.

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 propio montaje de medios: el script de inicialización sólo ejecuta chown sobre /app, /config y /defaults, por lo que /data o /downloads conserva la propiedad que tiene en el host. Segundo, el grupo compartido: si el cliente de descargas y Sonarr se ejecutan con GID distintos, ninguno puede modificar los archivos del otro. Asigne el mismo PGID a todos los contenedores de la pila. Tercero, la umask: si UMASK no está definido o está establecido en 022, los archivos nuevos se crean como 0644, sin permiso de escritura para el grupo. Por tanto, los demás contenedores del grupo pueden leerlos, pero no modificarlos. Establezca UMASK=002 y active el bit setgid en los directorios con chmod 2775 para que los archivos nuevos hereden el grupo.