Como resolver problemas de permissão em diretórios montados no Docker: 5 soluções práticas

Atualizado em 08/06/2026: confirmei novamente que estas cinco soluções continuam válidas nas versões atuais do Docker, incluindo o compartilhamento de arquivos do Docker Desktop com Apple Virtualization, o Docker rootless e o userns-remap. Também acrescentei links para outros textos da série. A conclusão continua a mesma: não use chmod 777; priorize --user ou crie um usuário no Dockerfile.
Aquela mensagem vermelha no terminal: “Permission denied”. Pela quinta vez nesta noite. O container de desenvolvimento funciona perfeitamente no Mac, mas falha assim que chega ao servidor Linux de produção. Você tenta apagar os logs gerados pelo container e o sistema diz que você não tem permissão, embora seja o administrador do servidor.
Ontem, um colega comentou: “Por que não usar chmod 777?”. Você testou e, de fato, funcionou. Ainda assim, ficou aquela dúvida: isso é realmente seguro?
Segundo estatísticas dos fóruns da comunidade Docker, 40% dos usuários iniciantes enfrentam problemas de permissão em diretórios montados, e 60% recorrem ao chmod 777. O resultado é um risco maior de escape de container e vazamento de dados.
Primeiro, vamos entender a origem do problema: o que UID e GID realmente significam. Depois, veremos cinco soluções adequadas, desde um ajuste temporário e simples até configurações de segurança corporativa, além de três comandos que permitem encontrar a causa em cerca de um minuto.
Causa principal: por que surgem problemas de permissão
UID e GID são as identidades reais
Você talvez imagine que o Linux identifica usuários pelo nome. Não é assim. O kernel do Linux reconhece números: UID (ID do usuário) e GID (ID do grupo). O nome de usuário é apenas um rótulo legível para pessoas.
Por exemplo, execute o comando id no seu computador:
uid=1000(oden) gid=1000(oden) groups=1000(oden)
Percebeu? O número 1000 é sua identificação real. Para o kernel, o nome oden não é relevante.
Agora veja o usuário root:
uid=0(root) gid=0(root) groups=0(root)
O usuário de número 0 é o superusuário. Não importa qual seja o nome: se o UID for 0, ele terá o nível mais alto de permissão no sistema.
Como o conflito de permissões acontece
Aqui está o ponto central. Você inicia o Docker no host como um usuário comum, por exemplo UID=1000, mas o container é executado como root, UID=0, por padrão. É aí que surge o conflito.
O fluxo completo é este:
- No host Linux, você inicia o container como um usuário comum com UID=1000
- O processo dentro do container é executado como root, UID=0, por padrão
- O root do container cria um arquivo, por exemplo
/app/logs/output.log - Por meio de um bind mount, esse arquivo aparece no host como
./logs/output.log - No host, o proprietário do arquivo é exibido como root, UID=0
- Você, como usuário comum de UID=1000, tenta apagá-lo, mas não tem permissão
É direto assim. O container não sabe quem você é no host; ele só reconhece o UID. Um arquivo criado pelo usuário 0 não pode ser alterado livremente por um usuário que não seja o 0.
Por que isso não acontece no macOS e no Windows?
Você pode pensar: “Estranho, nunca tive esse problema ao usar Docker no Mac”.
Isso ocorre porque o Docker Desktop no macOS e no Windows roda em uma máquina virtual. O macOS usa o framework Apple Virtualization — antes, usava o hyperkit — e o Windows usa WSL2 ou Hyper-V. Entre o container e o host, há uma camada adicional que traduz permissões.
No macOS, o sistema de arquivos VirtioFS costuma converter automaticamente o proprietário dos arquivos criados pelo container para o usuário atual do host. É conveniente, mas também explica por que um código funciona no Mac e falha ao chegar a um servidor Linux: no Linux, o Docker usa diretamente o kernel e não existe essa camada intermediária de conversão.
Em outras palavras, o Docker Desktop fez uma concessão em favor da experiência do usuário e reduziu um pouco a fidelidade ao comportamento do Linux. Durante o desenvolvimento, você não percebe o problema; na implantação, ele aparece.
Outros pontos de atenção
Bind mount vs. volume nomeado:
- O bind mount (
-v /host/path:/container/path) mapeia diretamente um diretório do host, por isso os problemas de permissão aparecem com mais clareza - O volume nomeado (
-v mydata:/container/path) é gerenciado pelo Docker e tende a ser mais flexível, embora também possa apresentar conflitos
SELinux e AppArmor:
Se o Linux usa SELinux, como em CentOS/RHEL, ou AppArmor, como no Ubuntu, a situação fica mais complexa. Além de alinhar UID e GID, você precisa considerar os rótulos de contexto de segurança. Diante de um erro de permissão sem causa aparente, consulte primeiro os logs do SELinux:
sudo ausearch -m avc -ts recent
Seu usuário não existe dentro do container:
Normalmente, a imagem contém apenas o root e alguns usuários do sistema. O usuário de UID=1000 que existe no host pode não existir no container. É por isso que o proprietário de alguns arquivos aparece apenas como um número.
Diagnóstico rápido: três comandos para encontrar o problema
Não entre em pânico ao encontrar Permission denied. O diagnóstico pode ser feito com três comandos em cerca de um minuto.
Comando 1: verificar o proprietário real do arquivo
ls -ln /your/mount/path
Observe que usamos -ln, e não -l. A opção -l exibe nomes de usuário; -ln mostra os números de UID e GID.
Exemplo de saída:
-rw-r--r-- 1 0 0 1024 Dec 17 10:00 output.log
Como interpretar essa saída?
- A primeira coluna,
-rw-r--r--, contém os bits de permissão, mas não é o foco agora - A segunda coluna,
1, é a quantidade de hard links e não é importante neste diagnóstico - A terceira coluna,
0, é o UID do proprietário ← este é o ponto principal - A quarta coluna,
0, é o GID do proprietário ← este também importa - Depois vêm tamanho, data, hora e nome do arquivo
Viu o 0 0? Ele representa o root. Se o seu UID no host é 1000, você naturalmente não poderá alterar esse arquivo.
Compare com uma situação normal:
ls -ln ~/my-project
Saída:
-rw-r--r-- 1 1000 1000 2048 Dec 17 11:30 README.md
Agora aparece 1000 1000: esse arquivo pertence a você.
Comando 2: verificar a identidade real do processo no container
docker exec <container_name> id
Exemplo de saída:
uid=0(root) gid=0(root) groups=0(root)
Isso revela com qual identidade o processo do container está sendo executado. Na maioria dos casos, será root, UID=0.
Compare com o host:
id
Saída:
uid=1000(oden) gid=1000(oden) groups=1000(oden),4(adm),27(sudo)
A diferença fica clara: dentro do container é 0; no host é 1000. Os valores não correspondem, e essa é a origem do conflito.
Comando 3: inspecionar a configuração de montagem do Docker
docker inspect <container_name> | grep -A 10 "Mounts"
A saída será semelhante a esta:
"Mounts": [
{
"Type": "bind",
"Source": "/home/oden/project/logs",
"Destination": "/app/logs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
O que verificar?
Type: é bind ou volume? Problemas de permissão são mais evidentes em bind mountsSource: é o caminho no host; executels -lnnele para conferir o proprietárioRW:truesignifica leitura e gravação;false, somente leituraMode: há alguma opção especial de montagem, como:zou:Zpara SELinux?
Fluxo de diagnóstico em um minuto
Ao encontrar um problema de permissão, verifique nesta ordem:
- Inspecione o arquivo: use
ls -lnpara ver UID/GID do arquivo problemático - Inspecione o container: use
docker exec <container> idpara ver a identidade do processo - Compare os valores: se o UID do container e o UID do proprietário do arquivo forem diferentes do seu UID no host, há um conflito de permissões
- Confirme a configuração: use
docker inspectpara verificar o tipo e o caminho da montagem
Veja um exemplo prático. Suponha que você não consiga apagar um log criado pelo container:
# Etapa 1: verificar o proprietário do arquivo
$ ls -ln ./logs/
-rw-r--r-- 1 0 0 5120 Dec 17 12:00 app.log
# UID=0: o arquivo foi criado pelo root
# Etapa 2: verificar a identidade do container
$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)
# O container realmente é executado como root
# Etapa 3: verificar minha identidade
$ id
uid=1000(oden) gid=1000(oden) ...
# Eu sou 1000, o container é 0: os valores não correspondem
# Diagnóstico: o container roda como root e cria arquivos pertencentes ao root;
# por isso, meu usuário não pode apagá-los
Com esse diagnóstico, fica mais fácil escolher a solução adequada.
Cinco soluções: escolha a mais adequada ao seu caso
Agora que a causa e o diagnóstico estão claros, vamos resolver o problema. Há cinco opções, da mais simples à mais complexa, de um ajuste temporário a uma configuração corporativa. O importante é saber em qual cenário usar cada uma.
Solução 1: definir UID/GID em tempo de execução com —user
Para quem serve: testes rápidos ou ambiente de desenvolvimento local
Como funciona: você diz diretamente ao Docker para executar o container com o seu UID. Assim, os arquivos criados pelo container passam a pertencer a você.
Como usar:
# Pela linha de comando
docker run --user $(id -u):$(id -g) -v /host/data:/app/data myimage
# Pelo docker-compose.yml
services:
myapp:
image: myimage
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./data:/app/data
Ao iniciar:
export UID=$(id -u)
export GID=$(id -g)
docker-compose up
Vantagens:
- É a opção mais simples e funciona imediatamente
- Não exige alterar o Dockerfile nem reconstruir a imagem
- É adequada para iterações rápidas no desenvolvimento local
Desvantagens:
- É preciso informar o usuário a cada inicialização
- Se a aplicação depender de um UID específico, como nginx vinculando a porta 80 com privilégios de root, ela poderá falhar
- Os membros da equipe podem ter UIDs diferentes, portanto não convém fixar o número
Índice de risco: baixo
Sistemas compatíveis: suporte ideal no Linux; funciona no macOS e no Windows, mas a experiência é diferente por causa da camada de máquina virtual
Quando usar: desenvolvimento local, testes temporários e validação rápida. Por exemplo, se tudo funciona no Mac e o problema surge no CI Linux, esta solução serve como correção imediata.
Solução 2: criar um usuário correspondente no Dockerfile
Para quem serve: imagens compartilhadas por uma equipe e cenários de uso recorrente
Como funciona: no momento da build, você passa o UID do host como build arg e cria um usuário correspondente na imagem. O container, então, é iniciado com esse usuário.
Como usar:
Dockerfile:
FROM python:3.11
# Recebe os argumentos de build
ARG UID=1000
ARG GID=1000
# Cria o grupo e o usuário
RUN groupadd -g $GID appuser && \
useradd -m -u $UID -g $GID appuser
# Define o diretório de trabalho e ajusta a propriedade
WORKDIR /app
RUN chown -R appuser:appuser /app
# Muda para o usuário não root
USER appuser
# Os comandos seguintes são executados como appuser
COPY --chown=appuser:appuser . /app
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Build:
docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) -t myapp:latest .
docker-compose.yml:
services:
myapp:
build:
context: .
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
volumes:
- ./data:/app/data
Vantagens:
- Depois da build, todas as execuções usam a identidade correta
- O container tem um ambiente completo para o usuário, incluindo diretório home e configurações de shell
- É uma solução profissional, adequada para produção
Desvantagens:
- Exige alterar o Dockerfile
- Se os membros da equipe usam UIDs diferentes, cada um precisa construir sua própria imagem
- Não é suficiente se a aplicação precisar de privilégios de root na inicialização, por exemplo para alterar configurações do sistema
Índice de risco: baixo
Sistemas compatíveis: ideal no Linux; também funciona no macOS e no Windows, apesar das diferenças causadas pela máquina virtual
Quando usar: quando a equipe mantém uma imagem base padronizada ou quando você distribui uma imagem, por exemplo em um projeto open source, e permite que cada usuário faça a build com seu próprio UID.
Solução 3: ajustar dinamicamente com um script de entrypoint e gosu
Para quem serve: aplicações que precisam iniciar como root e depois reduzir privilégios
Como funciona: o container inicia o script de entrypoint como root. O script cria dinamicamente um usuário e usa o gosu, semelhante ao sudo, mas mais apropriado para containers, para executar o processo principal com menos privilégios.
Como usar:
Dockerfile:
FROM node:18
# Instala o gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
# Copia o script de entrypoint
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
WORKDIR /app
COPY . /app
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", "server.js"]
entrypoint.sh:
#!/bin/bash
set -e
# Se a variável LOCAL_USER_ID foi definida
if [ -n "$LOCAL_USER_ID" ]; then
# Cria o usuário, caso ele ainda não exista
useradd -u $LOCAL_USER_ID -o -m appuser 2>/dev/null || true
# Altera o proprietário do diretório /app
chown -R appuser:appuser /app
# Usa o gosu para executar o comando seguinte como appuser
exec gosu appuser "$@"
else
# Sem um UID definido, executa como root
exec "$@"
fi
Execução:
docker run -e LOCAL_USER_ID=$(id -u) -v ./data:/app/data myapp
Vantagens:
- Oferece máxima flexibilidade: permite inicializar como root e executar o programa principal com menos privilégios
- A mesma imagem pode ser reutilizada por usuários com UIDs diferentes
- Tem boa segurança; o gosu é mais adequado do que su/sudo neste contexto
Desvantagens:
- Exige modificar o Dockerfile e o entrypoint
- Aumenta a complexidade e o custo de manutenção
- O gosu precisa ser instalado como dependência adicional, embora seja pequeno
Índice de risco: médio; o gosu é recomendado nos exemplos oficiais do Docker
Sistemas compatíveis: todos os sistemas
Quando usar: quando a aplicação precisa alterar configurações durante a inicialização com privilégios de root, mas deve executar seu processo principal como usuário comum. Um exemplo é um serviço que faz a inicialização privilegiada e depois reduz os privilégios dos workers.
Solução 4: User Namespace Remapping (userns-remap)
Para quem serve: empresas cuja política de segurança exige isolamento obrigatório e não permite que containers sejam root no host
Como funciona: uma configuração no nível do daemon do Docker remapeia automaticamente todos os UIDs do container para uma faixa de subusuários. Dentro do container, o processo acredita ser root, UID=0; no host, ele é um usuário comum, por exemplo UID=100000.
Como usar:
Edite /etc/docker/daemon.json:
{
"userns-remap": "default"
}
Reinicie o Docker:
sudo systemctl restart docker
O Docker cria automaticamente o usuário dockremap e atribui faixas de UID/GID em /etc/subuid e /etc/subgid.
Validação:
# Inicia o container
docker run -d --name test -v /tmp/test:/data busybox sleep 3600
# Dentro do container, ele parece ser root
docker exec test id
# uid=0(root) gid=0(root)
# No host
ls -ln /tmp/test
# O proprietário é um número alto, por exemplo 100000
Vantagens:
- Uma única configuração vale para todo o sistema
- Todos os containers são isolados automaticamente, sem alterar imagens ou comandos
- Oferece alta segurança: mesmo em caso de escape, o processo chega ao shell de um subusuário, não ao root real
- É uma solução corporativa recomendada pelo Docker
Desvantagens:
- Exige configuração do sistema e afeta todos os containers
- Containers e volumes existentes podem ficar incompatíveis e precisar ser recriados
- Não pode ser usado junto com o modo rootless
- Algumas operações privilegiadas, como
mount, continuam indisponíveis
Índice de risco: baixo, pois é uma solução oficial
Sistemas compatíveis: somente Linux, com suporte do kernel a user namespaces
Quando usar: quando a política da empresa exige isolamento para todos os containers; em ambientes multi-tenant com imagens não confiáveis; ou quando você quer uma solução global em vez de configurar cada projeto separadamente.
Solução 5: Docker rootless
Para quem serve: ambientes com exigência máxima de segurança que aceitam algumas limitações de funcionalidade
Como funciona: o próprio daemon do Docker é executado como usuário não root. Todos os containers ficam no namespace desse usuário e são isolados do root do sistema.
Como usar:
Instale o Docker rootless:
# Remove o Docker root, se estiver instalado
sudo apt-get remove docker docker-engine docker.io
# Instala o Docker rootless
curl -fsSL https://get.docker.com/rootless | sh
# Configura as variáveis de ambiente conforme as instruções
export PATH=$HOME/bin:$PATH
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
# Inicia o serviço
systemctl --user start docker
systemctl --user enable docker
Validação:
docker run hello-world
# Executado completamente sem privilégios de root
Vantagens:
- É a opção de segurança mais restritiva: o daemon não é root, e os containers também não têm acesso ao root real
- Mesmo em caso de escape, o processo permanece limitado às permissões do usuário
- É adequado para imagens não confiáveis, ambientes multi-tenant e cenários sensíveis à segurança
Desvantagens:
- Não pode usar portas privilegiadas abaixo de 1024, incluindo 80 e 443
- Não oferece alguns modos de rede, como o modo host
- O desempenho pode ser um pouco menor devido ao custo adicional dos namespaces
- A configuração é relativamente complexa e há menos documentação disponível
Índice de risco: baixo; o design é consistente e tem suporte oficial do Docker
Sistemas compatíveis: Linux moderno com suporte a newuidmap/newgidmap, como Ubuntu 20.04+ e CentOS 8+
Quando usar: quando a política de segurança é extremamente rígida, como em empresas financeiras ou de saúde; quando você executa imagens de terceiros não confiáveis; ou quando o cluster Kubernetes exige pods não root e você quer aplicar a mesma abordagem ao Docker nas máquinas de produção.
Decisão rápida: qual solução escolher?
Se, depois de ver as cinco opções, você ainda não sabe qual escolher, use esta árvore de decisão:
Encontrou um problema de permissão?
├─ É apenas um teste temporário?
│ └─ Sim → Solução 1 (parâmetro --user)
│
├─ É um projeto mantido pela equipe no longo prazo?
│ ├─ A aplicação precisa de root na inicialização?
│ │ └─ Sim → Solução 3 (entrypoint + gosu)
│ └─ Não precisa de root?
│ └─ Solução 2 (criar usuário no Dockerfile)
│
├─ A política de segurança exige isolamento obrigatório?
│ ├─ Precisa de portas privilegiadas ou recursos especiais?
│ │ └─ Sim → Solução 4 (userns-remap)
│ └─ Não precisa de privilégios?
│ └─ Solução 5 (Docker rootless)
│
└─ Quer apenas resolver rapidamente o desenvolvimento local?
└─ Solução 1 (parâmetro --user)
De modo geral, escolha assim:
- Ambiente de desenvolvimento: solução 1, rápida e eficaz
- Projeto de equipe: solução 2 ou 3, com configuração consistente
- Ambiente de produção: solução 4 ou 5, priorizando segurança
Não comece automaticamente pela opção mais complexa. Escolha conforme sua necessidade real. A solução suficiente para o cenário costuma ser a melhor.
Casos especiais entre plataformas: diferenças no macOS, Windows e Linux
“Na minha máquina funciona”
Essa frase é familiar? No desenvolvimento, tudo funciona no Mac, mas falha no servidor Linux. Ou acontece o contrário: no Linux não há problema, enquanto a máquina Windows apresenta comportamentos estranhos.
A razão está nas diferenças significativas de implementação do Docker entre as três plataformas.
Linux: o comportamento mais fiel e os problemas mais visíveis
No Linux, o Docker usa diretamente o kernel, sem uma máquina virtual intermediária. É o ambiente mais próximo da produção, mas também aquele em que os problemas de permissão aparecem com maior clareza.
Características:
- Container e host compartilham o mesmo kernel
- UID/GID são mapeados diretamente, sem conversão
- Por padrão, o container é executado como root, UID=0
- Conflitos de permissão em bind mounts aparecem diretamente
Boas práticas:
- No desenvolvimento, use a solução 1, com
--user, para resolver rapidamente - Em projetos de longo prazo, use a solução 2 e crie um usuário no Dockerfile
- Em produção, use a solução 4 ou 5, com
userns-remapou rootless
Problema comum:
# Não é possível apagar um arquivo criado pelo container
rm: cannot remove 'logs/app.log': Permission denied
# Verifique o proprietário
ls -ln logs/
# -rw-r--r-- 1 0 0 ...
# Causa: o container roda como root e cria arquivos de root
Solução: adicione user: "${UID}:${GID}" ao docker-compose.yml.
macOS: permissões mais flexíveis, mas com armadilhas
No macOS, o Docker Desktop é executado em uma máquina virtual leve com Apple Virtualization. O sistema de arquivos usa VirtioFS e faz conversão automática de permissões.
Características:
- O proprietário dos arquivos criados pelo container geralmente é convertido para o usuário atual do host
- Na maioria dos casos, você nem percebe o problema de permissão
- Essa conveniência pode causar falhas apenas no momento da implantação
Problemas conhecidos:
- Em 2023 e 2024, o VirtioFS apresentou vários bugs relacionados a permissões, como permissões incorretas em diretórios aninhados
- O Docker Desktop 4.13+ corrigiu a maioria deles, mas ainda existem casos extremos
- Permissões podem se perder ao atravessar várias camadas de links simbólicos
Boas práticas:
- No desenvolvimento local, você pode aproveitar essa conveniência sem configuração especial
- Não dependa dela: continue criando o usuário no Dockerfile, como na solução 2
- Antes da implantação, teste em uma máquina Linux ou em uma VM Linux
Armadilha comum:
# No Mac, esta configuração funciona
services:
app:
image: myapp
volumes:
- ./data:/app/data
# O container roda como root, mas o proprietário do arquivo é convertido para você
# No Linux, a implantação falha
# Todos os arquivos pertencem ao root, e o script de CI não consegue acessá-los
Solução: mesmo que não haja problema no Mac, adicione a configuração de usuário:
services:
app:
user: "${UID:-1000}:${GID:-1000}"
Windows: o cenário mais complexo
No Windows, o Docker Desktop roda em WSL2 ou Hyper-V. O modelo de permissões do NTFS é completamente diferente das ACLs do Linux.
Características:
- Modo WSL2: é relativamente próximo do Linux, mas há conversão ao atravessar NTFS e ext4
- Modo Hyper-V: acrescenta outra camada de virtualização e torna a conversão mais complexa
- Unidades protegidas por BitLocker podem apresentar comportamentos de permissão ainda menos intuitivos
Problemas comuns:
# Bind mount na unidade C:
docker run -v C:\Users\oden\project:/app myimage
# Permissões inconsistentes: às vezes é possível ler, mas não gravar
# Bind mount por um caminho do WSL
docker run -v /mnt/c/Users/oden/project:/app myimage
# Funciona melhor, mas ainda pode haver problemas
Boas práticas:
- Priorize volumes nomeados em vez de bind mounts:
services: db: image: postgres volumes: - pgdata:/var/lib/postgresql/data # Usa um volume volumes: pgdata: # Gerenciado pelo Docker, evitando conflitos com NTFS - Se o bind mount for indispensável, mantenha o projeto dentro do sistema de arquivos do WSL2 (
\\wsl$\Ubuntu\home\...) - Evite montagens entre unidades diferentes
Problemas conhecidos:
- Ao montar a unidade C: ou outra partição NTFS, os bits de permissão podem aparecer todos como 777; isso parece alarmante, mas o acesso real é controlado pelo NTFS
- O suporte a links simbólicos no Windows é limitado, e eles podem não aparecer no container
- Diferenças de fim de linha, LF vs. CRLF, podem se misturar a configurações do Git e do Docker
Trabalho em equipe entre plataformas: uma estratégia comum
O que fazer quando parte da equipe usa Mac, outra parte Linux e outra Windows?
Configuração recomendada:
docker-compose.yml:
services:
app:
build:
context: .
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
user: "${UID:-1000}:${GID:-1000}"
volumes:
- ./src:/app/src
Dockerfile:
FROM node:18
ARG UID=1000
ARG GID=1000
RUN groupadd -g $GID appuser && \
useradd -m -u $UID -g $GID appuser
WORKDIR /app
RUN chown appuser:appuser /app
USER appuser
.env.example, compartilhado pela equipe:
# Usuários de Linux/macOS executam
# export UID=$(id -u)
# export GID=$(id -g)
# Usuários de Windows podem manter valores fixos
UID=1000
GID=1000
Instruções no README.md:
## Iniciar o projeto
**Usuários de Linux/macOS**:
```bash
export UID=$(id -u) GID=$(id -g)
docker-compose up
Usuários de Windows:
# Execute no WSL2 ou use diretamente docker-compose up com o valor padrão 1000
docker-compose up
**Pontos principais**:
- Use build args e variáveis de ambiente para manter a configuração flexível
- Usuários de Linux informam seu UID real; macOS e Windows podem usar o valor padrão
- Crie o usuário no Dockerfile para manter o comportamento da imagem consistente entre plataformas
- Documente as diferenças de cada sistema
### Resumo em uma frase
- **Linux**: o problema é mais evidente, há mais opções de solução e o comportamento é mais próximo da produção
- **macOS**: geralmente funciona, mas não confie apenas nessa conveniência; mantenha uma configuração consistente
- **Windows**: prefira volumes a bind mounts e mantenha o projeto no sistema de arquivos do WSL2
Para equipes multiplataforma, build args e a configuração `user` permitem manter um comportamento previsível para todos.
## Casos práticos: como resolver situações comuns
Já vimos a causa, o diagnóstico, as soluções e as diferenças entre plataformas. Agora vamos aplicar tudo isso aos cinco cenários mais comuns de problemas de permissão.
### Caso 1: não é possível apagar logs no desenvolvimento local
**Sintoma**:
Você executa um container de aplicação que gera logs. Depois de algum tempo, tenta limpá-los:
```bash
rm -rf logs/
# rm: cannot remove 'logs/app.log': Permission denied
Diagnóstico:
# Etapa 1: verificar o proprietário
$ ls -ln logs/
total 1024
-rw-r--r-- 1 0 0 524288 Dec 17 14:30 app.log
-rw-r--r-- 1 0 0 524288 Dec 17 14:31 error.log
# UID=0: criado pelo root
# Etapa 2: verificar a identidade do container
$ docker exec myapp id
uid=0(root) gid=0(root) groups=0(root)
# O container é executado como root
# Etapa 3: verificar minha identidade
$ id
uid=1000(oden) gid=1000(oden) groups=1000(oden)
# Eu sou 1000, o container é 0: os valores não correspondem
Solução:
Altere o docker-compose.yml e acrescente a configuração user:
services:
myapp:
image: myapp:latest
user: "${UID:-1000}:${GID:-1000}" # Linha principal
volumes:
- ./logs:/app/logs
Execute:
export UID=$(id -u)
export GID=$(id -g)
docker-compose down
docker-compose up
O container passa a usar seu UID, e você será o proprietário dos logs criados por ele.
Resumo em uma frase: acrescente uma linha com user e o problema está resolvido.
Caso 2: permissões de arquivos estáticos em uma aplicação Django/Flask
Sintoma:
Sua aplicação web em Python precisa coletar arquivos estáticos. Depois de executar collectstatic:
docker exec webapp python manage.py collectstatic
# O diretório static/ foi criado
ls -ln static/
# drwxr-xr-x 1 0 0 ...
# O proprietário é root; o script de CI ou o container nginx não consegue acessar
Causa:
O container é executado como root, então os arquivos pertencem ao root. Se outro container nginx precisar servi-los, o usuário do nginx pode não ter permissão de leitura.
Solução:
Crie um usuário da aplicação no Dockerfile:
FROM python:3.11
# Cria o usuário da aplicação
RUN groupadd -g 1000 appuser && \
useradd -m -u 1000 -g 1000 appuser
WORKDIR /app
# Copia e instala as dependências ainda como root
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copia o código da aplicação e ajusta a propriedade
COPY --chown=appuser:appuser . /app
# Muda para appuser
USER appuser
# Os comandos seguintes usam appuser
CMD ["gunicorn", "myapp.wsgi:application"]
docker-compose.yml:
services:
webapp:
build: .
volumes:
- static_volume:/app/static
nginx:
image: nginx:alpine
volumes:
- static_volume:/usr/share/nginx/html/static:ro # Montagem somente leitura
ports:
- "80:80"
volumes:
static_volume:
Pontos principais:
- Crie no Dockerfile um usuário correspondente, com UID=1000
- Use um volume nomeado para compartilhar arquivos estáticos em vez de bind mount
- O container nginx lê o volume com seu próprio usuário, e o Docker gerencia as permissões
Resumo em uma frase: crie o usuário no Dockerfile e compartilhe os arquivos por um volume.
Caso 3: problemas de permissão em volumes de banco de dados
Sintoma:
Ao iniciar um container PostgreSQL ou MySQL, aparece um erro:
docker-compose up postgres
# postgres: could not open file "/var/lib/postgresql/data/...": Permission denied
Causa:
Imagens de banco de dados normalmente mudam para um UID específico, por exemplo o usuário postgres com UID=999. Se você usa bind mount para o diretório de dados, o proprietário no host pode não corresponder.
Diagnóstico:
# Verifica o diretório montado
ls -ln ./pgdata
# drwxr-xr-x 1 1000 1000 ...
# O proprietário é 1000, mas o container postgres precisa de 999
# Verifica o usuário da imagem postgres
docker run --rm postgres:15 id
# uid=999(postgres) gid=999(postgres) groups=999(postgres)
Solução:
Método A: usar um volume nomeado, recomendado
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql/data # Usa volume, não bind mount
volumes:
pgdata: # O Docker gerencia as permissões automaticamente
Método B: se o bind mount for obrigatório, prepare as permissões
# Cria o diretório e define o proprietário
mkdir -p ./pgdata
sudo chown -R 999:999 ./pgdata # Corresponde ao UID/GID do postgres
docker-compose.yml:
services:
postgres:
image: postgres:15
volumes:
- ./pgdata:/var/lib/postgresql/data
Atenção: o UID varia entre imagens e versões de banco de dados:
- PostgreSQL: 999
- MySQL: 999
- MongoDB: 999
- Redis: 999; por coincidência, muitas imagens usam esse número
Isso não é garantido para todas as versões. Confirme com docker run --rm <image> id.
Resumo em uma frase: para bancos de dados, use um volume nomeado e deixe o Docker gerenciar as permissões; se precisar de bind mount, execute chown antes.
Caso 4: propriedade incorreta dos artefatos em um pipeline de CI
Sintoma:
Seu pipeline de CI contém estas etapas:
# .gitlab-ci.yml
build:
script:
- docker run --rm -v $CI_PROJECT_DIR:/app builder npm run build
- ls -l dist/ # Verifica os artefatos da build
# -rw-r--r-- 1 root root ... (o proprietário é root)
- cp dist/* /deploy/ # Permission denied
O runner de CI usa um usuário comum, mas o container faz a build como root. Os artefatos pertencem ao root e as etapas seguintes não conseguem acessá-los.
Solução:
Método A: alterar explicitamente o proprietário dentro do container de build
# Dockerfile.builder
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
# Faz a build e ajusta o proprietário
RUN npm run build && \
chown -R 1000:1000 /app/dist
CMD ["npm", "run", "build"]
Método B: executar o container de build com —user
# .gitlab-ci.yml
build:
script:
- docker run --rm --user $(id -u):$(id -g) -v $CI_PROJECT_DIR:/app builder npm run build
- ls -l dist/ # Agora você é o proprietário
- cp dist/* /deploy/ # Funciona normalmente
Método C: tratar no entrypoint, com mais flexibilidade
FROM node:18
RUN apt-get update && apt-get install -y gosu
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
WORKDIR /app
ENTRYPOINT ["/entrypoint.sh"]
CMD ["npm", "run", "build"]
entrypoint.sh:
#!/bin/bash
set -e
# Executa a build
npm run build
# Se OUTPUT_UID foi definido, altera o proprietário dos artefatos
if [ -n "$OUTPUT_UID" ]; then
chown -R $OUTPUT_UID:${OUTPUT_GID:-$OUTPUT_UID} /app/dist
fi
Configuração de CI:
build:
script:
- docker run --rm -e OUTPUT_UID=$(id -u) -v $CI_PROJECT_DIR:/app builder
Resumo em uma frase: defina explicitamente o proprietário dos artefatos ou execute o container de build com --user.
Caso 5: problemas de permissão em um Pod do Kubernetes
Sintoma:
Você implanta a aplicação no Kubernetes, mas o Pod não inicia:
kubectl logs mypod
# Error: EACCES: permission denied, open '/app/data/config.json'
Causa:
O securityContext do Kubernetes pode restringir o usuário do Pod, ou o fsGroup do volume pode estar incorreto.
Diagnóstico:
# Entra no Pod para verificar
kubectl exec -it mypod -- id
# uid=1000 gid=1000 groups=1000
# Verifica os arquivos do volume
kubectl exec -it mypod -- ls -ln /app/data
# drwxr-xr-x 2 0 0 ...
# O proprietário é root, mas o Pod usa 1000 e não consegue ler
Solução:
Defina o securityContext na especificação do Pod:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
securityContext:
runAsUser: 1000 # O Pod usa UID=1000
runAsGroup: 1000 # GID=1000
fsGroup: 1000 # O grupo dos arquivos do volume será 1000, com leitura e gravação
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: data
mountPath: /app/data
volumes:
- name: data
emptyDir: {}
Pontos principais:
runAsUser: UID do processo no containerrunAsGroup: GID do processo no containerfsGroup: proprietário de grupo dos arquivos do volume, permitindo leitura e gravação pelo processo
Se estiver usando um PersistentVolumeClaim:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
securityContext:
fsGroup: 1000 # O grupo dos arquivos do PVC será 1000
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 1000 # O processo usa UID 1000
volumeMounts:
- name: storage
mountPath: /app/data
volumes:
- name: storage
persistentVolumeClaim:
claimName: mypvc
Resumo em uma frase: defina explicitamente runAsUser e fsGroup no securityContext do Pod.
Resumo dos cinco casos
| Cenário | Sintoma | Solução | Método recomendado |
|---|---|---|---|
| Não é possível apagar logs locais | Permission denied | Configurar user | Adicionar user ao docker-compose.yml |
| Coleta de arquivos estáticos | nginx não consegue ler | Criar usuário no Dockerfile | USER appuser + volume |
| Falha ao iniciar o banco | Sem permissão no diretório de dados | Volume nomeado | Deixar o Docker gerenciar as permissões |
| Permissões dos artefatos de CI | Etapa seguinte não consegue acessar | —user ou entrypoint | Executar chown durante a build |
| Permissões de Pod no K8s | Erro EACCES | securityContext | runAsUser + fsGroup |
Cada cenário pede uma solução diferente. O ponto principal é diagnosticar primeiro, identificar qual identidade está em conflito e só então aplicar a correção adequada.
Leitura complementar
- Guia completo de Docker Volumes: como persistir dados corretamente
- Bind mount, volume ou tmpfs no Docker: qual montagem escolher
- Executar Docker sem root: reforço prático da segurança de containers
Conclusão
Lembra da situação do início, com você olhando para “Permission denied” e sem entender por que, mesmo como administrador, não conseguia apagar o arquivo?
Agora a causa está clara:
Causa principal: o Linux reconhece UID/GID, não nomes de usuário. Um arquivo criado pelo root do container, UID=0, não pode ser alterado livremente pelo usuário comum do host, UID=1000.
Diagnóstico: três comandos bastam. Use ls -ln para conferir o proprietário, docker exec <container> id para ver a identidade no container e docker inspect para examinar a montagem. Em cerca de um minuto, você encontra a origem do problema.
Soluções: escolha entre cinco opções:
- Parâmetro —user: ajuste rápido para testes locais
- Criar um usuário no Dockerfile: solução consistente para projetos mantidos pela equipe
- entrypoint + gosu: inicialização como root, seguida de execução com menos privilégios
- userns-remap: isolamento obrigatório em nível corporativo
- Docker rootless: segurança máxima, com algumas limitações de recursos
Diferenças entre plataformas: o Docker Desktop no macOS e no Windows inclui uma camada de conversão de permissões, por isso o problema é menos visível. No Linux, o Docker usa diretamente o kernel e expõe o conflito. Não deixe a conveniência do Mac esconder a necessidade de uma configuração consistente no Dockerfile.
Experiência prática: os cinco casos mostram como tratar permissões no desenvolvimento local, em arquivos estáticos, bancos de dados, builds de CI e implantações no Kubernetes. Cada contexto tem uma opção mais adequada.
O que fazer a partir de hoje
Ainda hoje, em cinco minutos:
- Adicione
user: "${UID:-1000}:${GID:-1000}"ao seu docker-compose.yml - Execute
docker exec <container> idpara verificar o UID real dos containers do projeto - Use
ls -lnpara diagnosticar um problema de permissão
Nesta semana, em uma ou duas horas:
- Melhore o Dockerfile com
ARG UID/GIDe a criação de um usuário - Adicione os comandos de diagnóstico ao wiki ou README da equipe
- Compartilhe este texto com os colegas, se ele tiver ajudado
No longo prazo:
- Se a política de segurança da empresa exigir, avalie
userns-remapou o modo rootless - Revise a configuração do Docker em produção e confirme o isolamento de permissões
- Acrescente verificações de permissão ao pipeline de CI/CD
Para encerrar
Problemas de permissão parecem técnicos e tediosos, mas no fundo são problemas de identidade. O container não sabe quem você é no host; ele só reconhece números.
Quando você entende o mapeamento de UID/GID, tudo fica mais simples. Não é preciso recorrer cegamente ao chmod 777 nem perder horas com erros de permissão.
Escolha a solução adequada, use os comandos certos e entenda não apenas o que fazer, mas por que funciona.
Problema resolvido.
27 min de leitura · Publicado em: 17 dez 2025 · Atualizado em: 4 set 2026
Guia prático Docker
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
Comparativo de montagens no Docker: guia para escolher Volume ou Bind Mount (com teste de desempenho)
Comparativo detalhado dos três tipos de montagem do Docker, com solução para o npm install até 3 vezes mais lento no Mac, árvore de decisão e cenários reais para escolher entre Volume, Bind Mount e tmpfs.
Parte 16 de 34
Próximo
Modos de rede do Docker: desempenho e quando usar bridge, host, none e container
Entenda como funcionam os quatro modos de rede do Docker — bridge, host, none e container —, compare o desempenho e escolha a configuração adequada com exemplos práticos.
Parte 18 de 34



Comentários
Entre com GitHub para comentar