Volumes no Docker Compose: bind mount ou nomeado?
Saiba quando usar bind mounts ou volumes nomeados no Docker Compose, evite erros de permissao e aprenda a inspecionar, fazer backup e migrar dados.
Bind mount ou volume nomeado: a resposta curta
Os volumes do Docker Compose têm dois tipos, e a escolha depende de quem gere os ficheiros. Use um bind mount para ficheiros que escreve e lê diretamente, como configuração, modelos e sites estáticos. Use um volume nomeado para dados geridos pela aplicação, como ficheiros de bases de dados, índices de pesquisa e ficheiros multimédia enviados. Um bind mount aponta para um caminho no host que pode abrir num editor. Um volume nomeado é armazenamento que o Docker cria e gere, e ao qual acede através do Docker.
Ambos aparecem sob a mesma chave volumes: dentro de um serviço, razão pela qual são frequentemente confundidos. A diferença está no lado esquerdo dos dois pontos. Um lado esquerdo que começa por . ou / é um caminho do host, portanto é um bind mount. Qualquer outro valor é um nome, portanto é um volume nomeado, e esse nome também tem de ser declarado no bloco volumes: de nível superior.
As duas sintaxes num ficheiro Compose
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: nginx:1.27
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:pgdata:/var/lib/postgresql/data é um volume nomeado. ./nginx.conf:/etc/nginx/nginx.conf é uma montagem bind, e :ro monta-o apenas para leitura, que é a predefinição correta para uma configuração que um contentor nunca deve reescrever. Se esquecer a entrada de nível superior volumes:, o Compose termina com service "db" refers to undefined volume pgdata.
Inicie os serviços e liste o que o Docker criou:
docker compose up -d
docker volume lsO volume não se chama pgdata. Chama-se <project>_pgdata, porque, por predefinição, o nome do projeto é o nome do diretório que contém o ficheiro Compose. Um diretório chamado myapp resulta em myapp_pgdata. Isto é importante porque mudar o nome do diretório cria um novo volume vazio, e a aplicação parece ter perdido os dados. Não perdeu: o volume antigo continua listado por docker volume ls. Fixe o nome com name: no ficheiro Compose ou defina COMPOSE_PROJECT_NAME se o diretório puder mudar de localização. Definições desse tipo devem ficar junto dos seus outros ficheiros de ambiente e segredos do Compose.
Por que os erros de permissões só afetam bind mounts
Esta é a maior diferença prática e resulta de uma regra: um volume nomeado vazio na primeira utilização é inicializado a partir da imagem; um bind mount nunca é.
Quando o Docker monta um volume nomeado vazio sobre um diretório que já contém dados na imagem, copia esses dados para o volume, mantendo o proprietário e os modos definidos pela imagem. A imagem oficial do Postgres inclui /var/lib/postgresql/data pertencente ao utilizador postgres, pelo que o volume fica pertencente ao mesmo ID numérico e a base de dados arranca.
Um bind mount faz o contrário. O que existe no host é o que o contentor vê, incluindo o proprietário, e o conteúdo da imagem nesse caminho fica oculto. Se o diretório do host não existir, o daemon do Docker cria-o. Como o daemon é executado como root, obtém-se um diretório pertencente a root:root. Um processo do contentor executado com um utilizador não root não consegue escrever nesse diretório:
PermissionError: [Errno 13] Permission denied: '/data/app.db'A correção consiste em fazer coincidir os números. A propriedade através de um bind mount é comparada pelo ID numérico do utilizador, não pelo nome, porque o contentor tem o seu próprio /etc/passwd. Um utilizador chamado app dentro do contentor não tem qualquer significado no host. O uid 1000 significa uid 1000 em ambos os lados.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id mostra o uid com que o processo do contentor é efetivamente executado. Altere o proprietário do diretório no host para esse número ou fixe o contentor ao seu número com user: "1000:1000" no serviço. Fixar user: é mais simples numa aplicação desenvolvida por si. Alterar o proprietário do diretório no host é mais seguro para uma imagem que não desenvolveu, porque algumas imagens iniciam um entrypoint como root, reduzem os privilégios e esperam encontrar proprietários específicos nos diretórios inferiores.
Convém conhecer mais duas armadilhas. No Fedora, RHEL e noutros sistemas com SELinux (security-enhanced Linux) em modo enforcing, um bind mount é bloqueado até receber a etiqueta correta. Por isso, adicione :z a um caminho partilhado entre contentores ou :Z a um caminho que apenas um contentor deve utilizar, escrito como - ./data:/data:Z. Além disso, um bind mount de um único ficheiro, em vez de um diretório, deixa de funcionar quando um editor substitui o ficheiro em vez de escrever no mesmo local, porque o mount segue o inode original. O contentor continua a ver o conteúdo antigo até ser reiniciado. Monte o diretório-pai quando o ficheiro for editado com frequência.
Desempenho: onde a diferença é real
Num servidor Linux, os dois tipos seguem o mesmo caminho no kernel. Por isso, a diferença de throughput é pequena e não deve ser um critério de escolha. Os volumes nomeados que usam o driver padrão local ficam no mesmo sistema de ficheiros que o resto do Docker, em /var/lib/docker/volumes/. Um bind mount fica no local que definiu.
A diferença aparece no Docker Desktop para macOS e Windows, onde os contentores são executados numa máquina virtual. Nesse caso, um bind mount atravessa o sistema de ficheiros do host até à máquina virtual através de uma camada de partilha de ficheiros. Cargas de trabalho com muitas operações de ficheiros pequenos, como uma árvore de dependências do Node.js ou a cache de uma framework PHP, ficam visivelmente mais lentas. Os volumes nomeados permanecem dentro da máquina virtual e não têm esse custo. É por isso que tantos ficheiros Compose de desenvolvimento fazem bind mount do diretório do código-fonte, mas declaram um volume nomeado sobre node_modules.
A outra diferença real é o local onde os dados são gravados. Um bind mount para /mnt/backup grava os dados nesse disco. Um volume nomeado é gravado no sistema de ficheiros que contém /var/lib/docker, normalmente o disco raiz numa VPS. Uma base de dados que cresça dentro de um volume nomeado enche o mesmo disco onde estão os logs do sistema. Verifique o espaço antes que isso cause um incidente:
docker system df -v
df -h /var/lib/dockerdocker system df -v lista todos os volumes com o respetivo tamanho e marca os volumes que já não são referenciados por nenhum contentor.
Inspecionar um volume nomeado
Um volume nomeado não é uma caixa-preta. Pergunte ao Docker onde ele está:
docker volume inspect myapp_pgdataO campo Mountpoint apresenta um caminho real no host, normalmente /var/lib/docker/volumes/myapp_pgdata/_data. Pode lê-lo com sudo ls, o que é útil para uma verificação rápida. Não o trate como um local para editar ficheiros. Escrever nesse caminho como root recria o problema de propriedade descrito acima, e o caminho é um detalhe do controlador local que outros controladores de volumes não partilham.
A forma segura de consultar o conteúdo é usar um contentor temporário que monte o volume:
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volIsto funciona com qualquer controlador, apresenta as mesmas permissões que o contentor real vê e não deixa resíduos devido a --rm.
Fazendo backup de cada tipo
Um bind mount é um diretório comum, portanto qualquer ferramenta de backup ao nível de ficheiros já o trata. Aponte o backup para o caminho no host e está concluído. Um volume nomeado requer um passo adicional, porque a ferramenta precisa de aceder ao seu conteúdo. Monte o volume e um diretório do host no mesmo contentor temporário e, em seguida, crie um arquivo:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Restaure invertendo o processo num volume novo:
docker volume create myapp_pgdata_restored
docker run --rm \
-v myapp_pgdata_restored:/data \
-v "$PWD":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /datatar preserva a propriedade numérica quando é executado como root dentro do contentor. É isso que mantém o volume restaurado utilizável pela aplicação.
Há uma ressalva aplicável aos dois tipos. Copiar os ficheiros de uma base de dados enquanto ela está em execução cria um arquivo de um estado em mudança e pode restaurá-lo num estado corrompido. Pare o serviço primeiro ou faça um dump através da própria ferramenta da base de dados, como em docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Isso produz um ficheiro simples que pode depois incluir numa rotina normal de backup encriptado com restic, juntamente com os ficheiros do compose.
Migrar um bind mount para um volume nomeado
A operação faz uma cópia, não uma renomeação, e demora cerca de um minuto.
docker compose down
docker volume create myapp_pgdata
docker run --rm \
-v "$PWD/data":/from \
-v myapp_pgdata:/to \
alpine sh -c 'cp -a /from/. /to/'cp -a preserva o proprietário, as permissões e os timestamps. Assim, o utilizador do contentor que conseguia ler o diretório antigo continua a conseguir ler o novo volume. Depois, altere o serviço para usar pgdata:/var/lib/postgresql/data, adicione pgdata ao bloco volumes: de nível superior, execute docker compose up -d e consulte os logs da aplicação antes de eliminar o diretório antigo. Para fazer o processo inverso, use o mesmo comando, trocando /from e /to de posição.
Tenha um aspeto em mente durante os testes. docker compose down não altera os volumes nomeados, mas docker compose down -v elimina todos os volumes nomeados declarados pelo projeto e não existe forma de desfazer essa operação. Um bind mount sobrevive a ambos, porque o Docker nunca foi proprietário desse diretório. Se os comandos do ciclo de vida ainda forem novos para si, o guia básico do Docker Compose para um VPS explica-os passo a passo.
Escolha serviço a serviço
Pergunte quem escreve o ficheiro. A configuração que edita num editor de texto e envia para o git deve ficar num bind mount, montado em :ro, porque precisa de estar visível e sob controlo de versões. O estado da aplicação que nunca abre manualmente deve ficar num volume nomeado, porque o Docker define as permissões corretamente e os dados não dependem de um caminho no host.
O caso misto é o conteúdo multimédia. Uma biblioteca de fotografias é escrita pela aplicação, mas também é gerida por si, e muitas vezes é suficientemente grande para justificar um disco específico. Monte-a por bind mount num caminho desse disco e defina explicitamente o proprietário uma vez. Este é o padrão adotado pela maioria das stacks self-hosted: volumes nomeados para bases de dados e caches, bind mounts para a configuração e para o diretório grande que pretende preservar. Um serviço de suporte como o Chatwoot a funcionar numa VPS enquadra-se exatamente nesse modelo, com o Postgres num volume nomeado e os anexos carregados num caminho que pode indicar a uma cópia de segurança.
FAQ
Qual é a diferença entre um bind mount e um volume nomeado?
Um bind mount mapeia um caminho do host para dentro do contentor. Assim, ambos veem o mesmo diretório e pode editá-lo com ferramentas normais. Um volume nomeado é um armazenamento que o Docker cria e gere. É referenciado pelo nome e declarado no bloco de nível superior volumes:. Na prática, a diferença está na gestão: use bind mounts para configurações que mantém e volumes nomeados para dados que a aplicação mantém.
Por que motivo recebo "permission denied" com um bind mount, mas não com um volume nomeado?
Um volume nomeado vazio é inicializado a partir da imagem. Por isso, herda o proprietário definido pela imagem e o utilizador do contentor pode escrever nele. Um bind mount apresenta o diretório do host exatamente como está. Se o Docker tiver de criar esse diretório, irá criá-lo com root como proprietário. Execute docker compose exec <service> id para ver o ID numérico usado pelo contentor. Depois, execute sudo chown -R <uid>:<gid> no diretório do host ou defina user: "1000:1000" no serviço.
Onde é que o Docker armazena os volumes nomeados no disco?
Com o driver predefinido local, os volumes ficam em /var/lib/docker/volumes/<volume>/_data. docker volume inspect <volume> mostra o Mountpoint exato. Consulte esse caminho se precisar de verificar alguma coisa, mas escreva nele apenas através de um contentor. Editá-lo como root no host altera a propriedade dos ficheiros de uma forma que o contentor não espera.
Como faço uma cópia de segurança de um volume nomeado?
Execute um contentor de curta duração com o volume e um diretório do host montados. Depois, crie um arquivo de um para o outro com docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. Numa base de dados, faça um dump com a própria ferramenta da base de dados em vez de copiar ficheiros ativos. Uma cópia feita enquanto existem operações de escrita em curso pode ser restaurada para um estado corrompido.
O docker compose down elimina os meus volumes?
docker compose down remove os contentores e as redes, mas mantém os volumes nomeados. docker compose down -v também elimina permanentemente todos os volumes nomeados declarados pelo projeto. Nenhum dos comandos remove bind mounts, porque esse diretório pertence ao host e não ao Docker.