Volumes no Docker: a pasta que aparece a pertencer a ninguém
O sintoma aparece sempre da mesma maneira. O contentor escreve numa pasta montada, vai-se ver ao anfitrião, e os ficheiros pertencem a um número em vez de a uma pessoa:
ls -l dados/
# -rw-r--r-- 1 999 999 8192 Sep 13 10:14 base.dbOu então o contrário: a pasta é sua, tem as permissões certas, e o contentor insiste em não conseguir escrever lá.
1. A causa é uma só
Já escrevi isto a propósito das permissões no Linux e repete-se aqui porque é a chave de tudo: o sistema de ficheiros não guarda nomes, guarda números. O nome que o ls mostra é uma tradução feita na hora, a partir do /etc/passwd.
Um contentor tem o seu próprio /etc/passwd. O núcleo é o mesmo, o número é o mesmo — só a tabela de tradução é outra.
id -u
# 1000
docker run --rm alpine id -u
# 0, e ali chama-se rootNão há camada nenhuma a converter. Se o processo lá dentro corre como 999, os ficheiros que ele criar pertencem ao 999 cá fora — quer isso corresponda a alguém, quer não.
2. Três formas de montar, e só uma dá problemas
# Bind mount: a pasta pertence ao anfitrião, e o dono vem de lá
docker run -v "$PWD/dados:/dados" a-minha-imagem
# Volume nomeado: o Docker cria-o, e se estiver vazio copia
# dono e permissões da pasta que existe dentro da imagem
docker run -v dados:/dados a-minha-imagemÉ esta diferença que explica a confusão mais comum — «com volume nomeado funciona, com bind mount não». O volume nomeado, quando nasce vazio, herda o dono de dentro da imagem. O bind mount não herda nada: a pasta já existia e já tinha dono.
3. As saídas, pela ordem que eu tentaria
Não escrever em disco. É a melhor, e é a que menos se considera. Este site corre em contentor sem volume nenhum: as imagens, o conteúdo e a sessão vivem na base de dados ou na imagem, e o servidor não grava ficheiros. O Dockerfile acaba com uma linha só sobre o assunto:
# Não há razão para correr como root: o servidor não escreve em disco.
USER nodeQuem não precisa de escrever não tem este problema, e ganha ainda a parte de segurança: um contentor sem permissão de escrita é muito menos interessante para quem entrar nele.
Volume nomeado em vez de bind mount, quando os dados só interessam ao contentor — a pasta de dados de um Postgres é o exemplo perfeito. Ninguém precisa de os abrir no anfitrião, e o Docker trata das permissões.
Acertar o UID na invocação, quando o bind mount é mesmo preciso:
docker run --user "$(id -u):$(id -g)" -v "$PWD:/app" -w /app node:22 npm run buildFunciona, mas convém saber o que custa: o processo passa a correr com um número que não existe no /etc/passwd do contentor. Programas que perguntem quem são — ou que queiram uma pasta pessoal — passam a falhar de formas pouco óbvias. O npm é um dos que se queixa.
Ou fazer como as imagens oficiais: arrancar como root, corrigir o dono da pasta montada, e só depois baixar de privilégios antes de entregar o processo à aplicação. É o que o entrypoint do Postgres faz, e é a razão de aquela imagem funcionar sem ninguém pensar no assunto.
4. O caso do node_modules
O clássico do desenvolvimento. Monta-se o projeto inteiro para ter recarregamento automático, e o node_modules do anfitrião — compilado para outro sistema — tapa o que a imagem tinha instalado:
docker run -v "$PWD:/app" -v /app/node_modules a-minha-imagemAquele segundo -v sem origem cria um volume anónimo por cima da pasta, e é o que impede o de fora de a tapar. Parece um truque, e é — mas é o truque certo.
5. O aviso que interessa mesmo
No Docker Desktop, em Windows ou em macOS, os ficheiros atravessam uma máquina virtual e uma camada de tradução que faz a propriedade parecer certa. Nada disto dói.
Em Linux não há camada nenhuma, e o UID é o UID. Ou seja: o problema não aparece na sua máquina, aparece no servidor. Se o contentor vai correr em Linux, é em Linux que isto tem de ser experimentado — e mais vale descobri-lo antes do primeiro deploy do que a meio dele.
O resumo
Antes de montar uma pasta, uma pergunta: alguém, fora do contentor, precisa mesmo de abrir estes ficheiros? Se a resposta for não, um volume nomeado resolve e o assunto acaba ali. Se for sim, então é preciso decidir de propósito com que número o processo corre — porque o que o contentor não faz é adivinhá-lo por si.