SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-09-23

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 ciertas imágenes de contenedor leen durante el arranque. Docker no las consulta. Son una convención que usan las imágenes de linuxserver.io y algunas otras. Por eso, una imagen que no está preparada para leerlas las ignora sin mostrar ningún error.

Dentro de una imagen de linuxserver.io existe un usuario llamado abc. Se crea 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 esos scripts cambia el ID de ese usuario antes de continuar:

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 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 queden en el disco con el propietario 1000. 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é sus archivos aparecen como 911:911

ls -l muestra un ID numérico en lugar de un nombre cuando ningún usuario del host coincide con ese ID. Ninguna cuenta 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 evitar 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

Esa salida indica que el contenedor se ejecutó con los valores predeterminados integrados. El config.xml de ese listado 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 comprueba que Sonarr y Radarr se distribuyen sin nombre de usuario ni contraseña predeterminados. Confírmelo desde dentro del contenedor en lugar de hacer suposiciones:

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

La 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, 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 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 aplicar 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 abandona los privilegios. Docker Mods y los scripts de inicialización personalizados siguen funcionando. El coste es que se confía en una convención en lugar de 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 elemento 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 concretas: 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 estético sorprende a algunos usuarios. Un user: numérico no tiene ninguna entrada coincidente 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 el cálculo de propietarios. El UID 0 del contenedor se asigna al UID del usuario del host que ejecuta Docker rootless, y 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 en /etc/subuid y /etc/subgid. Docker espera que haya al menos 65,536 IDs subordinados allí.

Revise de nuevo esa asignación, 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 que pertenecen a un ID subordinado situado aproximadamente en 100999, al que su shell no puede acceder. Por tanto, el valor de PUID correcto en un daemon rootful es incorrecto aquí. Ambos mecanismos resuelven el mismo problema en capas diferentes, y combinarlos sin comprobarlo es la forma de acabar con un directorio que necesita sudo para eliminarlo. Si utiliza rootless, pruebe la propiedad de un archivo escrito en su propio servidor antes de migrar una biblioteca.

Para la mayoría de las pilas autoalojadas en un único VPS, PUID y PGID en un daemon con privilegios de root son la opción práctica, porque es para lo que están creadas y documentadas 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. Un espacio de trabajo documental como una instancia autoalojada de AFFiNE en un único VPS pertenece a este último caso, porque ninguno de sus contenedores lee PUID y la propiedad de su directorio de base de datos y de los archivos cargados la determina el runtime, no ningún elemento del bloque de entorno. Lo mismo ocurre con una mesa de ayuda autoalojada de Chatwoot, donde el contenedor de Rails y su worker de Sidekiq escriben en un único directorio de cargas y ninguno de los dos lee PUID, por lo que 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í que 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, lo que convierte el problema en uno de user: y chown, no de PUID. Si coloca una única API autoalojada 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 una 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. Sonarr crea un enlace duro a ese archivo o lo mueve 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 usar un grupo compartido que todos los contenedores de la pila utilicen 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 todos los archivos y subdirectorios nuevos creados dentro heredan el grupo media en lugar del grupo primario propio del creador. Así, la configuración sigue funcionando con 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 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. De este modo, 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 también se aplica a los contenedores que están más abajo en la cadena y sólo leen la biblioteca terminada, como Jellyfin y los frontends que se le añaden, como Halcyon, que sirve esa biblioteca como un videoclub transitable de los años 90.

A continuación, establezca UMASK=002 en cada contenedor linuxserver de la pila. Este es el paso que suele omitirse. El valor predeterminado de estas imágenes es UMASK=022. Este valor elimina el permiso de escritura del grupo en todos los archivos nuevos. Por eso, los archivos se crean como 0644 y la configuración de uso compartido no sirve de nada. 002 produce archivos 0664 y directorios 0775, y el grupo puede escribir en ellos:

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 de Compose. Así, toda la pila lee una única definición:

PUID=1000
PGID=13000

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

Verifique el funcionamiento completo en lugar de confiar sólo 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, falta el bit setgid en ese directorio. Si el modo aparece como -rw-r--r--, la variable UMASK no se aplicó. Compruebe que haya recreado 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 usa nombres distintos para la misma idea: USERMAP_UID y USERMAP_GID, ambos con un valor predeterminado de 1000. Su documentación indica que debe obtenerlos 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 upstream, incluidas las imágenes habituales de bases de datos y servidores web, incluyen un usuario fijo integrado y esperan que use 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 gestor de entrenamientos openGym autoalojado, conviene comprobar con qué usuario se ejecuta realmente su contenedor antes de apuntar un bind mount hacia él. El directorio que contiene su base de datos hereda esa configuración, establezca PUID o no. Un relay de acceso remoto pertenece a la misma categoría. Cuando ejecute su propio servidor relay de RustDesk, el par de claves Ed25519 que hbbs escribe en el primer arranque aparece en el bind mount con el propietario del usuario que la imagen utilizó al finalizar. Un chown en el host es la única corrección disponible. Lo mismo se aplica a la infraestructura que añada más adelante. Si coloca Authentik delante de sus aplicaciones para centralizar el inicio de sesión, ejecutará imágenes oficiales de server, Postgres y Redis que no leen PUID. El propietario de los volúmenes dependerá del runtime y no de un entrypoint configurable.

Por tanto, revise 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, independientemente de que algú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 figure al final de su propio Dockerfile. Puede identificarlo mediante el propietario de los archivos que escribe. Revise esta información antes de añadir cualquier componente al servidor, incluido un stack autoalojado de análisis de seguridad open-kritt. El archivo Compose indica si las imágenes respetan PUID o si las propias imágenes fijan el propietario de los directorios que monta.

FAQ

¿Por qué los archivos de Docker son propiedad de 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 definir PUID y PGID, por lo que su script de inicio mantuvo los valores predeterminados integrados. ls -l muestra los números sin traducir porque ningún usuario del host tiene el ID 911 y, por tanto, no hay ningún nombre que mostrar. Defina 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 para ejecutarse sin root. En una imagen de linuxserver, definir user: hace que PUID y PGID no tengan efecto, 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 todavía no puede mover archivos. ¿Cuál es el problema?

Compruebe tres aspectos en este orden. Primero, el propio montaje de medios: el script de inicio 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 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 permiso de escritura para el grupo, lo que inutiliza por completo un grupo compartido. Defina UMASK=002 y establezca el bit setgid en los directorios con chmod 2775 para que los archivos nuevos hereden el grupo.