O que PUID e PGID fazem no Docker Compose
PUID e PGID nao sao configuracoes do Docker: sao convencoes de entrypoint da linuxserver.io. Entenda por que bind mounts ficam como 911:911 e corrija isso.
O que PUID e PGID realmente são
PUID e PGID são duas variáveis de ambiente que determinadas imagens de contentores leem durante o arranque. O Docker nunca as consulta. Trata-se de uma convenção usada pelas imagens linuxserver.io e por algumas outras. Por isso, uma imagem que não foi concebida para as ler ignora-as silenciosamente.
Numa imagem linuxserver.io existe um utilizador chamado abc, criado durante o build com UID (ID do utilizador) 911 e GID (ID do grupo) 911. O contentor arranca como root, executa os seus scripts de inicialização e um desses scripts altera o ID desse utilizador antes de qualquer outra operação:
groupmod -o -g "${PGID}" abc
usermod -o -u "${PUID}" abcO sinalizador -o permite usar um ID que já esteja em utilização noutro local. Depois disso, o processo de inicialização remove os privilégios e executa a aplicação como abc. Portanto, PUID=1000 nunca chega ao Docker. A variável altera o ID de um utilizador dentro do contentor antes de a aplicação arrancar. Isto faz com que todos os ficheiros que a aplicação grava no disco sejam propriedade de 1000. Se não definir PUID, abc mantém 911. É por isso que um bind mount não configurado fica cheio de ficheiros pertencentes a 911:911.
Obtenha os dois números com id
Execute isto no host, como o utilizador proprietário dos diretórios de dados:
iduid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),988(docker)uid é o seu PUID e gid é o seu PGID. Num script, id -u e id -g imprimem apenas os números. Na maioria das imagens VPS novas, a primeira conta humana é 1000:1000, mas não assuma esse valor. Um servidor reconstruído ou uma segunda conta adicionada posteriormente pode usar 1001 ou superior. Um número incorreto aqui é a causa de todo o problema. Se os seus serviços forem executados com uma conta de serviço dedicada em vez do seu utilizador de login, execute id thatuser e use os números apresentados.
Por que os seus ficheiros aparecem como 911:911
ls -l mostra um ID numérico em vez de um nome quando não existe uma conta no host correspondente a esse ID. Não existe nenhum utilizador com UID 911 no servidor, portanto não há um nome para mostrar. Use ls -ln para mostrar sempre os números e eliminar a ambiguidade:
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.xmlEsse resultado indica que o contentor foi executado com os valores predefinidos incorporados. Confirme isso a partir do interior do contentor, em vez de presumir:
docker exec sonarr id abc
docker compose logs sonarr | head -n 25O init do linuxserver apresenta o resultado no log de arranque, em duas linhas:
User UID: 911
User GID: 911Se essas linhas mostrarem 911 depois de definir PUID=1000 no seu ficheiro Compose, a variável nunca chegou ao contentor. A causa habitual é ter editado docker-compose.yml e executado depois docker compose restart, que reutiliza o contentor existente com o ambiente original. As alterações de ambiente exigem docker compose up -d, que recria o contentor.
Por que não é possível apagar um ficheiro escrito pelo contentor
O kernel compara números, nunca nomes. A sua shell é executada como UID 1000. O ficheiro pertence ao UID 911. O diretório que o contém é drwxr-xr-x e também pertence a 911, por isso o grupo e os outros utilizadores têm permissões de leitura e execução, mas não de escrita. Apagar um ficheiro exige permissão de escrita no diretório que o contém, não no ficheiro. Por isso, obtém este resultado mesmo quando o próprio ficheiro parece inofensivo:
rm: cannot remove '/srv/appdata/sonarr/config.xml': Permission deniedUm contentor que escreve encontra o mesmo bloqueio pelo lado oposto. Se o diretório do host pertencer ao seu utilizador e tiver o modo 755, e a aplicação for executada como 911, a primeira escrita falha com Permission denied e a aplicação comunica o erro nos seus próprios termos. Numa aplicação .NET como Sonarr ou Radarr, isso aparece como UnauthorizedAccessException: Access to the path '/data/downloads' is denied. A cadeia de permissões antes do nome do ficheiro indica qual dos três conjuntos de permissões está efetivamente a ser aplicado ao seu caso. Ler drwxr-xr-x corretamente é o que transforma esse erro de misterioso em óbvio.
Este é especificamente um problema de bind mount. Quando o Docker cria um volume nomeado vazio e o monta sobre um caminho existente na imagem, copia para o volume o conteúdo desse caminho, incluindo o proprietário e os bits de permissão. Assim, a aplicação encontra um diretório que já lhe pertence. Um bind mount não recebe esse tratamento. O Docker monta o diretório do host exatamente como está. Essa diferença é uma das razões práticas para saber quando um bind mount é melhor do que um volume nomeado e quando não é.
Corrigir um diretório que já está incorreto
Definir PUID e PGID altera o comportamento da aplicação daqui em diante. Isso não corrige retroativamente os ficheiros que já estão no disco. Pare a stack, corrija manualmente o proprietário e inicie-a novamente:
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 se preferir não escrever os números. Faça isto com o contentor parado, porque uma aplicação em execução que esteja a escrever durante um chown recursivo pode deixar a árvore de diretórios corrigida apenas parcialmente e causar uma segunda série de erros difícil de diagnosticar.
O que PUID e PGID não corrigem
Esta é a parte que afeta quem fez tudo corretamente. O init do linuxserver executa chown exatamente em três caminhos durante o arranque: /app, /config e /defaults. As montagens de media não estão nessa lista. /data, /downloads e /tv são entregues à aplicação sem alterações. Portanto, se o lado do host dessas montagens tiver uma propriedade que impeça o utilizador do contentor de escrever, o contentor arranca sem erros, apresenta o UID correto no banner e falha na primeira importação.
Esse é o comportamento correto. Executar um chown recursivo numa biblioteca de media com doze terabytes sempre que o contentor arranca seria desastroso. Isto significa que a configuração dos diretórios de media é da sua responsabilidade. São também essas montagens que normalmente apresentam problemas de permissões. Como esta falha aparece silenciosamente num log da aplicação horas depois de o contentor parecer saudável, um teste periódico de escrita integrado com ntfy no seu próprio VPS, enviando alertas para o seu telemóvel é uma forma simples de detetar o problema antes de uma semana sem episódios o revelar.
Três formas de controlar o utilizador e quando aplicar cada uma
Variáveis de ambiente PUID e PGID
Isto funciona apenas em imagens cujo entrypoint as lê. É popular porque o contentor continua a iniciar como root, faz a sua própria configuração, corrige /config e só depois reduz os privilégios. Docker Mods e scripts de inicialização personalizados continuam a funcionar. O custo é confiar numa convenção em vez de numa funcionalidade da plataforma, e os nomes das variáveis não são padronizados entre projetos.
A chave user: no Compose
Esta é uma funcionalidade real do Docker e funciona com qualquer imagem, porque o runtime do contentor aplica-a antes de o código da própria imagem ser executado:
services:
sonarr:
image: lscr.io/linuxserver/sonarr:latest
user: "1000:1000"O processo nunca é executado como root, nem por um instante, o que representa um ganho de segurança efetivo. Também interrompe tudo o que no entrypoint precisava de root. Nas imagens linuxserver, o projeto oferece suporte com base em esforços razoáveis e apenas para as imagens que testou. As limitações são específicas: PUID e PGID deixam de ter efeito, Docker Mods não são executados, os serviços personalizados não são executados e passa a ser responsável pelas permissões de todos os volumes montados. O padrão documentado combina a flag com um /run com permissões de escrita:
user: 1000:1000
tmpfs:
- /run:uid=1000,gid=1000,exec
security_opt:
- no-new-privileges=trueUm efeito apenas cosmético surpreende algumas pessoas. Um user: numérico não tem uma entrada correspondente no /etc/passwd do contentor, pelo que as ferramentas internas mostram whoami: cannot find name for user ID 1000. O ID é válido e o acesso aos ficheiros funciona normalmente. Apenas a resolução do nome falha.
Docker rootless
O Docker rootless executa o próprio daemon com o seu utilizador sem privilégios, pelo que nada no sistema é executado como root real. Isto altera completamente o cálculo das propriedades. O UID 0 do contentor é associado ao UID do utilizador no host que executa o Docker rootless, e o UID n do contentor, para qualquer n de 1 ou superior, é associado a subuid + (n - 1), em que subuid é a base do intervalo atribuído ao utilizador em /etc/subuid e /etc/subgid. O Docker espera pelo menos 65,536 IDs subordinados nesse local.
Leia novamente esse mapeamento, porque ele inverte a recomendação habitual. Com o Docker rootless, um contentor que escreve como root cria ficheiros pertencentes ao seu utilizador. Um contentor que escreve como UID 1000 cria ficheiros pertencentes a um ID subordinado próximo de 100999, que a sua shell não consegue alterar. Portanto, o valor PUID correto num daemon rootful é o valor errado neste caso. Os dois mecanismos resolvem o mesmo problema em camadas diferentes. Combiná-los sem verificar é uma forma de acabar com um diretório que precisa de sudo para ser removido. Se optar pelo modo rootless, teste a propriedade de um ficheiro criado no seu próprio servidor antes de migrar uma biblioteca para lá.
Para a maioria das stacks self-hosted num único VPS, PUID e PGID num daemon rootful são a escolha pragmática, porque é para isso que as imagens são compiladas e documentadas. Use user: quando o README da imagem indicar que ela é testada com essa configuração ou quando estiver a executar uma imagem oficial do upstream que não tenha suporte algum para PUID. Um workspace de documentos como uma instância self-hosted do AFFiNE num único VPS enquadra-se neste último caso, porque nenhum dos seus contentores lê PUID, e a propriedade do diretório da base de dados e dos ficheiros carregados é definida pelo runtime, não por nada no bloco de ambiente. O mesmo se aplica a um service desk self-hosted do Chatwoot, em que o contentor Rails e o worker Sidekiq escrevem no mesmo diretório de uploads e nenhum deles lê PUID; por isso, esse diretório tem de corresponder ao utilizador com que a imagem já é executada. Isto também não muda numa stack mais recente. Assim, dar a cada pessoa da equipa o seu próprio agente OneCLI isolado deixa os diretórios de workspace de cada pessoa e o diretório de dados do Postgres pertencentes ao utilizador com que cada imagem já é executada, o que faz deste um problema de user: e chown, e não de PUID. Se colocar uma única API self-hosted à frente do Codex, Claude Code e Hermes, herdará a mesma configuração, porque essa imagem é executada com o seu próprio utilizador integrado, e o bind mount que contém a base de dados e as chaves armazenadas assume a propriedade desse utilizador.
O caso da stack de multimédia: um grupo partilhado entre contentores
Uma stack de multimédia arr com Sonarr, Radarr e um cliente de downloads é onde isto deixa de ser teoria. O cliente de downloads escreve um ficheiro concluído em /data/downloads. Em seguida, o Sonarr cria uma hardlink para esse ficheiro ou move-o para /data/media. Para a hardlink funcionar, os dois contentores precisam de acesso de escrita à mesma árvore. Se o cliente de downloads for executado como 1000 e o Sonarr como 1001, um deles será proprietário dos ficheiros que o outro apenas consegue ler.
A solução é um grupo partilhado que todos os contentores da stack usam 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 {} +O 2 inicial em 2775 é o bit setgid. Num diretório, isto significa que todos os ficheiros e subdiretórios novos criados dentro dele herdam o grupo media em vez do grupo primário do criador. Assim, a configuração continua a funcionar para novos downloads sem ser necessário voltar a executar chown. Termine a sessão e inicie-a novamente, ou execute newgrp media, antes de verificar o seu próprio acesso. Um grupo adicionado com usermod -aG não aparece numa sessão de shell já aberta.
Dentro do contentor, groupmod -o -g 13000 abc renumera o grupo abc para 13000. Assim, abc escreve com o mesmo GID que o grupo media no host. Cada contentor da stack mantém o seu próprio PUID e partilha esse único PGID. Isto inclui os contentores mais abaixo na cadeia que apenas leem a biblioteca concluída, como o próprio Jellyfin e as interfaces que as pessoas ligam a ele, como Halcyon, que disponibiliza essa biblioteca como uma videolocadora dos anos 90 que pode ser percorrida.
Depois, defina UMASK=002 em todos os contentores linuxserver da stack. Este é o passo que muitas pessoas esquecem. O valor predefinido nestas imagens é UMASK=022. Ele remove o bit de escrita do grupo de todos os ficheiros novos. Como resultado, os ficheiros são criados como 0644 e a partilha que acabou de configurar não tem efeito. 002 cria ficheiros 0664 e diretórios 0775, permitindo que o grupo escreva:
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-stoppedEstes dois valores devem ficar num ficheiro .env junto do ficheiro Compose, para que toda a stack leia uma única definição:
PUID=1000
PGID=13000O Compose lê automaticamente esse ficheiro para a substituição ao estilo ${PUID}. É o mesmo mecanismo usado para credenciais. As práticas de manter os valores fora de docker-compose.yml e num ficheiro .env também se aplicam aqui. A diferença é que estes dois números não são secretos.
Verifique o funcionamento de ponta a ponta em vez de confiar apenas na configuração. Escreva um ficheiro dentro de um contentor e leia-o a partir do host:
docker exec sonarr touch /data/downloads/permtest
ls -ln /srv/media/downloads/permtestUm resultado correto mostra o seu PUID como proprietário, 13000 como grupo e -rw-rw-r-- como modo. Se o modo do grupo for 1000, falta o bit setgid nesse diretório. Se o modo for -rw-r--r--, a variável UMASK não teve efeito. Nesse caso, confirme que recriou o contentor em vez de apenas o reiniciar. Remova o ficheiro de teste com rm /srv/media/downloads/permtest quando terminar.
Quais imagens usam quais variáveis
As imagens linuxserver.io usam PUID, PGID e UMASK. O Paperless-ngx usa nomes diferentes para a mesma ideia: USERMAP_UID e USERMAP_GID, ambos com o valor predefinido 1000. A documentação manda obtê-los a partir de id -u e id -g. Os servidores de fotografias mostram a mesma variedade: o PhotoPrism tem o seu próprio par PHOTOPRISM_UID e PHOTOPRISM_GID, enquanto o Immich não fornece um equivalente e deixa o utilizador do contentor ao critério da chave user: do Docker. Assim, escolher entre PhotoPrism e Immich também determina qual destes mecanismos terá de manter para a maior biblioteca do servidor. Muitas imagens oficiais dos projetos originais, incluindo as imagens comuns de bases de dados e servidores Web, usam um utilizador fixo integrado e esperam que use user: ou que não altere nada. As implementações pequenas de uma única aplicação levantam a mesma questão. Por isso, quando configura um monitor de exercícios openGym autoalojado, confirme qual é o utilizador efetivo do contentor antes de apontar um bind mount para ele. O diretório que contém a base de dados seguirá essa definição, independentemente de definir PUID. Um relay de acesso remoto pertence à mesma categoria. Quando executa o seu próprio servidor relay RustDesk, o par de chaves Ed25519 que o hbbs grava no primeiro arranque aparece no seu bind mount com a propriedade do utilizador final dessa imagem. Um chown no host é a única correção disponível. O mesmo se aplica à infraestrutura que adicionar mais tarde. Colocar o Authentik à frente das suas aplicações para um único início de sessão significa executar as imagens oficiais do servidor, do Postgres e do Redis. Nenhuma delas lê PUID, e a propriedade dos volumes é determinada pelo runtime, não por um entrypoint configurável.
Por isso, consulte o README de cada imagem antes de copiar um bloco de ambiente entre projetos. O Docker passa qualquer variável de ambiente que definir para qualquer contentor, mesmo que nada no seu interior a leia. Um PUID que nenhum componente consome não produz erro, aviso nem efeito. O contentor é executado com o utilizador definido pelo próprio Dockerfile, e é através da propriedade dos ficheiros que ele grava que essa definição se torna evidente. Faça essa verificação antes de adicionar qualquer coisa ao servidor, incluindo uma stack de análise de segurança open-kritt autoalojada. O ficheiro Compose indica se as imagens respeitam PUID ou se a propriedade dos diretórios montados é determinada pelas próprias imagens.
FAQ
Por que os meus ficheiros Docker pertencem a 911:911?
911 é o UID e o GID do utilizador abc integrado nas imagens linuxserver.io. Isto indica que o contentor foi iniciado sem PUID e PGID definidos, pelo que o script de inicialização manteve os valores predefinidos integrados. ls -l apresenta os números sem conversão porque nenhuma conta no host tem o ID 911. Por isso, não existe nenhum nome para apresentar. Defina PUID e PGID com o resultado de id, recrie o contentor com docker compose up -d e corrija os ficheiros existentes com sudo chown -R 1000:1000 no diretório afetado.
PUID e PGID funcionam em todas as imagens Docker?
Não. Não são uma funcionalidade do Docker, e o Docker nunca os lê. Funcionam apenas em imagens cujo próprio entrypoint os lê e chama usermod e groupmod antes de iniciar a aplicação. Isto aplica-se à família linuxserver.io e a alguns projetos que copiaram este padrão. Outros projetos usam nomes diferentes, como USERMAP_UID e USERMAP_GID no paperless-ngx. Numa imagem que não leia nenhuma destas variáveis, elas são aceites e ignoradas sem qualquer aviso.
Devo usar PUID e PGID ou a chave user: no Docker Compose?
Use PUID e PGID quando a imagem os suportar. O entrypoint continua a ser executado como root durante tempo suficiente para corrigir /config e iniciar corretamente os próprios serviços. Use user: quando a imagem não tiver suporte para PUID ou quando o README da imagem indicar que a execução sem root foi testada. Numa imagem linuxserver, definir user: torna PUID e PGID inativos, impede a execução de Docker Mods e de serviços personalizados e deixa sob a sua responsabilidade as permissões de todos os volumes montados.
O Sonarr tem o PUID correto, mas continua sem conseguir mover ficheiros. Qual é o problema?
Verifique três pontos, pela ordem indicada. Primeiro, o próprio mount de media: o init apenas executa chown em /app, /config e /defaults. Por isso, /data ou /downloads mantém a propriedade que tem no host. Segundo, o grupo partilhado: se o cliente de downloads e o Sonarr forem executados com GIDs diferentes, nenhum deles poderá modificar os ficheiros do outro. Defina o mesmo PGID em todos os contentores da stack. Terceiro, o umask: o valor predefinido da imagem, UMASK=022, cria ficheiros como 0644 sem o bit de escrita para o grupo. Isto impede totalmente a utilização de um grupo partilhado. Defina UMASK=002 e ative o bit setgid nos diretórios com chmod 2775, para que os ficheiros novos herdem o grupo.