Alternar tema

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

Easton editorial illustration: one mounted folder passing through an ownership lock into a container

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.

40%
Usuários enfrentam problemas de permissão
40% dos iniciantes enfrentam o problema; 60% usam chmod 777

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:

  1. No host Linux, você inicia o container como um usuário comum com UID=1000
  2. O processo dentro do container é executado como root, UID=0, por padrão
  3. O root do container cria um arquivo, por exemplo /app/logs/output.log
  4. Por meio de um bind mount, esse arquivo aparece no host como ./logs/output.log
  5. No host, o proprietário do arquivo é exibido como root, UID=0
  6. 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 mounts
  • Source: é o caminho no host; execute ls -ln nele para conferir o proprietário
  • RW: true significa leitura e gravação; false, somente leitura
  • Mode: há alguma opção especial de montagem, como :z ou :Z para SELinux?

Fluxo de diagnóstico em um minuto

Ao encontrar um problema de permissão, verifique nesta ordem:

  1. Inspecione o arquivo: use ls -ln para ver UID/GID do arquivo problemático
  2. Inspecione o container: use docker exec <container> id para ver a identidade do processo
  3. 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
  4. Confirme a configuração: use docker inspect para 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-remap ou 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 container
  • runAsGroup: GID do processo no container
  • fsGroup: 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árioSintomaSoluçãoMétodo recomendado
Não é possível apagar logs locaisPermission deniedConfigurar userAdicionar user ao docker-compose.yml
Coleta de arquivos estáticosnginx não consegue lerCriar usuário no DockerfileUSER appuser + volume
Falha ao iniciar o bancoSem permissão no diretório de dadosVolume nomeadoDeixar o Docker gerenciar as permissões
Permissões dos artefatos de CIEtapa seguinte não consegue acessar—user ou entrypointExecutar chown durante a build
Permissões de Pod no K8sErro EACCESsecurityContextrunAsUser + 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

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:

  1. Parâmetro —user: ajuste rápido para testes locais
  2. Criar um usuário no Dockerfile: solução consistente para projetos mantidos pela equipe
  3. entrypoint + gosu: inicialização como root, seguida de execução com menos privilégios
  4. userns-remap: isolamento obrigatório em nível corporativo
  5. 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> id para verificar o UID real dos containers do projeto
  • Use ls -ln para diagnosticar um problema de permissão

Nesta semana, em uma ou duas horas:

  • Melhore o Dockerfile com ARG UID/GID e 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-remap ou 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

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog