SSD Nodes Learn 8GB de RAM — $66/ano
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-02

Volumes no Docker Compose: bind mount ou nomeado?

Entenda quando usar bind mount ou volume nomeado no Docker Compose, evite erros de permissão e saiba 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 é responsável pelos arquivos. Use um bind mount para arquivos que você mesmo grava e lê, como configurações, templates e sites estáticos. Use um volume nomeado para dados pelos quais a aplicação é responsável, como arquivos de banco de dados, índices de pesquisa e mídias enviadas. Um bind mount aponta para um caminho no host que você pode abrir em um editor. Um volume nomeado é um armazenamento que o Docker cria e gerencia para você, e que você acessa pelo Docker.

Ambos aparecem sob a mesma chave volumes: dentro de um serviço, por isso são confundidos. A diferença está no lado esquerdo dos dois-pontos. Um lado esquerdo que começa com . ou / é um caminho do host, portanto é um bind mount. Qualquer outra coisa é um nome, portanto é um volume nomeado, e esse nome também deve ser declarado no bloco volumes: de nível superior.

As duas sintaxes em um arquivo 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 conteúdo como somente leitura, que é o padrão correto para uma configuração que um contêiner nunca deve reescrever. Se você esquecer a entrada volumes: no nível superior, o Compose será encerrado 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 ls

O volume não se chama pgdata. Ele se chama <project>_pgdata, porque o nome do projeto, por padrão, é o nome do diretório que contém o arquivo Compose. Um diretório chamado myapp resulta em myapp_pgdata. Isso é importante porque renomear o diretório cria um novo volume vazio, e a aplicação parece ter perdido os dados. Ela não perdeu: o volume antigo continua listado por docker volume ls. Fixe o nome com name: no arquivo Compose ou defina COMPOSE_PROJECT_NAME se o diretório puder ser movido. Configurações desse tipo devem ficar junto dos seus outros arquivos de ambiente e secrets do Compose.

Por que os erros de permissão afetam apenas bind mounts

Essa é a maior diferença prática. Ela resulta de uma regra: um volume nomeado vazio no primeiro uso é inicializado a partir da imagem, mas um bind mount nunca é.

Quando o Docker monta um volume nomeado vazio sobre um diretório que já contém dados na imagem, ele copia esses dados para o volume, mantendo o proprietário e as permissões definidos pela imagem. A imagem oficial do Postgres fornece /var/lib/postgresql/data pertencente ao usuário postgres, portanto o volume fica pertencendo ao mesmo id numérico, e o banco de dados é iniciado.

Um bind mount faz o oposto. O conteúdo existente no host é o conteúdo que o container vê, inclusive o proprietário, e o conteúdo da imagem nesse caminho fica oculto. Se o diretório no host não existir, o daemon do Docker o criará, e o daemon será executado como root. Assim, você terá um diretório pertencente a root:root. Um processo do container executado por um usuário não root não poderá gravar nele:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

A correção é fazer os números corresponderem. O proprietário em um bind mount é comparado pelo id numérico do usuário, não pelo nome, porque o container tem seu próprio /etc/passwd. Um usuário chamado app dentro do container não representa nada no host. Uid 1000 significa uid 1000 nos dois lados.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id mostra o uid com o qual o processo do container é realmente executado. Altere o proprietário do diretório no host para esse número ou fixe o container no seu número usando user: "1000:1000" no serviço. Fixar user: é mais simples para uma aplicação que você mesmo desenvolveu. Alterar o proprietário do diretório no host é mais seguro para uma imagem que você não desenvolveu, porque algumas imagens iniciam um entrypoint como root, reduzem os privilégios e esperam proprietários específicos nos diretórios internos.

Vale conhecer mais duas armadilhas. No Fedora, RHEL e em outros sistemas com SELinux (security-enhanced Linux) no modo enforcing, um bind mount é negado até receber a reclassificação adequada. Portanto, adicione :z para um caminho compartilhado entre containers ou :Z para um caminho que somente um container deve usar, escrito como - ./data:/data:Z. Além disso, um bind mount de um único arquivo, em vez de um diretório, deixa de funcionar quando um editor substitui o arquivo em vez de gravar nele diretamente, porque o mount acompanha o inode original. O container continua vendo o conteúdo antigo até ser reiniciado. Monte o diretório pai quando o arquivo for editado com frequência.

Desempenho: onde a diferença é real

Em um servidor Linux, os dois tipos passam pelo mesmo caminho do kernel. Portanto, a diferença de throughput é pequena o suficiente para que você não escolha com base nisso. Os volumes nomeados que usam o driver padrão local ficam no mesmo sistema de arquivos que o restante do Docker, em /var/lib/docker/volumes/. Um bind mount fica no local que você definiu.

A diferença aparece no Docker Desktop para macOS e Windows, onde os containers são executados dentro de uma máquina virtual. Um bind mount nesse ambiente atravessa o sistema de arquivos do host até essa máquina virtual por meio de uma camada de compartilhamento de arquivos. Cargas de trabalho com muitas operações em arquivos pequenos, como uma árvore de dependências do Node.js ou um cache de framework PHP, ficam visivelmente mais lentas. Os volumes nomeados permanecem dentro da máquina virtual e não têm esse custo. Por isso, muitos arquivos Compose de desenvolvimento fazem bind mount do diretório de origem, mas declaram um volume nomeado sobre node_modules.

A outra diferença real é onde os bytes são armazenados. Um bind mount para /mnt/backup armazena os dados nesse disco. Um volume nomeado usa o sistema de arquivos que contém /var/lib/docker, que em uma VPS geralmente é o disco raiz. Um banco de dados que cresce dentro de um volume nomeado ocupa o mesmo disco onde estão os logs do sistema. Verifique isso antes que se torne um incidente:

docker system df -v
df -h /var/lib/docker

docker system df -v lista todos os volumes com seus tamanhos e marca aqueles que nenhum container referencia mais.

Inspecionando um volume nomeado

Um volume nomeado não é uma caixa-preta. Pergunte ao Docker onde ele está:

docker volume inspect myapp_pgdata

O campo Mountpoint fornece um caminho real no host, normalmente /var/lib/docker/volumes/myapp_pgdata/_data. Você pode lê-lo com sudo ls, o que é útil para uma verificação rápida. Não o trate como um local para editar arquivos. Escrever nesse local como root recria o problema de propriedade descrito acima, e o caminho é um detalhe do driver local que outros drivers de volume não compartilham.

A maneira segura de examinar o conteúdo é usar um contêiner temporário que monte o volume:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

Isso funciona com qualquer driver, mostra as mesmas permissões que o contêiner real vê e não deixa nada para trás por causa de --rm.

Backup de cada tipo

Um bind mount é um diretório comum, portanto qualquer ferramenta de backup em nível de arquivo já o trata. Aponte o backup para o caminho no host e pronto. Um volume nomeado exige uma etapa adicional, porque a ferramenta precisa acessar seu conteúdo. Monte o volume e um diretório do host no mesmo container temporário e grave um arquivo compactado:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

Restaure invertendo o processo em um 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 /data

tar preserva a propriedade numérica quando é executado como root dentro do container. Isso mantém o volume restaurado utilizável pela aplicação.

Há um aviso válido para os dois tipos. Copiar os arquivos de um banco de dados enquanto ele está em execução gera um arquivo compactado de um estado que está mudando e pode restaurá-lo em um estado corrompido. Pare o serviço primeiro ou faça um dump usando a própria ferramenta do banco de dados, como em docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Isso produz um arquivo simples que você pode incluir em uma rotina normal de backup criptografado com restic, junto com os arquivos do compose.

Migrando uma montagem bind para um volume nomeado

A operação é uma cópia, não uma renomeação, e leva 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 carimbos de data e hora. Assim, o usuário do contêiner que conseguia ler o diretório antigo continuará conseguindo ler o novo volume. Em seguida, altere o serviço para usar pgdata:/var/lib/postgresql/data, adicione pgdata ao bloco de nível superior volumes:, execute docker compose up -d e leia os logs da aplicação antes de excluir o diretório antigo. Para fazer o caminho inverso, use o mesmo comando trocando /from e /to de lugar.

Lembre-se de um ponto durante os testes. docker compose down deixa os volumes nomeados intactos, mas docker compose down -v exclui todos os volumes nomeados declarados pelo projeto, sem possibilidade de desfazer a operação. Uma montagem bind sobrevive a ambos, porque o Docker nunca foi o proprietário desse diretório. Se os comandos de ciclo de vida ainda forem novidade para você, o guia de conceitos básicos do Docker Compose para um VPS explica como usá-los.

Escolha serviço por serviço

Pergunte quem grava o arquivo. A configuração que você edita em um editor de texto e confirma no git deve ficar em um bind mount, montado em :ro, porque você quer que ela fique visível e versionada. O estado da aplicação que você nunca abre manualmente deve ficar em um volume nomeado, porque o Docker define as permissões corretamente e os dados não dependem de um caminho no host.

O caso misto é a mídia. Uma biblioteca de fotos é gravada pela aplicação, mas também é administrada por você, e geralmente é grande o suficiente para exigir um disco específico. Faça o bind mount em um caminho nesse disco e defina o proprietário deliberadamente uma vez. Esse é o padrão adotado pela maioria das stacks auto-hospedadas: volumes nomeados para bancos de dados e caches, bind mounts para a configuração e para o diretório grande que você quer controlar.

FAQ

Qual é a diferença entre um bind mount e um volume nomeado?

Um bind mount mapeia um caminho do host para dentro do container. Assim, os dois lados veem o mesmo diretório, e você pode editá-lo com ferramentas comuns. Um volume nomeado é um armazenamento criado e gerenciado pelo Docker, referenciado por nome e declarado no bloco de nível superior volumes:. A diferença prática é a responsabilidade pela manutenção: use bind mounts para configurações que você mantém e volumes nomeados para dados que a aplicação mantém.

Por que 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, ele herda a propriedade definida pela imagem, e o usuário do container pode gravar nele. Um bind mount mostra o diretório do host exatamente como ele está. Se o Docker precisou criar esse diretório, ele o criou pertencendo a root. Execute docker compose exec <service> id para ver o ID numérico usado pelo container. Em seguida, execute sudo chown -R <uid>:<gid> no diretório do host ou defina user: "1000:1000" no serviço.

Onde o Docker armazena os volumes nomeados no disco?

Com o driver padrão local, eles ficam em /var/lib/docker/volumes/<volume>/_data, e docker volume inspect <volume> exibe o Mountpoint exato. Leia esse caminho se precisar verificar algo, mas grave nele somente por meio de um container. Editá-lo como root no host altera a propriedade de uma forma que o container não espera.

Como faço backup de um volume nomeado?

Execute um container de curta duração com o volume e um diretório do host montados. Depois, arquive os dados 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 .. Para um banco de dados, faça um dump usando a própria ferramenta do banco em vez de copiar arquivos em uso. Uma cópia feita enquanto há gravações em andamento pode ser restaurada em um estado corrompido.

O docker compose down exclui meus volumes?

docker compose down remove containers e redes, mas mantém os volumes nomeados. docker compose down -v também exclui permanentemente todos os volumes nomeados declarados pelo projeto. Os bind mounts nunca são removidos por nenhum dos dois comandos, porque esse diretório pertence ao host, não ao Docker.