Todos os artigos
148 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Tutoriais

Volumes Docker: bind mount, volume nomeado e tmpfs

O filesystem do container some no docker rm e quebra dados de banco. Veja os tres tipos de volume, quando usar cada um, permissoes de UID e backup.

Volumes Docker: bind mount, volume nomeado e tmpfs
COVER · Tutoriais

Você sobe um Postgres num container, cria as tabelas, popula com dados de teste e tudo funciona. Aí roda docker rm para limpar e, na próxima subida, o banco está vazio. Não é bug: o filesystem do container é efêmero por design. Cada container é uma camada gravável fina por cima das image layers, e essa camada morre junto com o container. Qualquer dado escrito ali — arquivos de banco, uploads, logs — desaparece no docker rm, e isso quebra exatamente as coisas que você mais precisa preservar.

A solução é tirar os dados de dentro da camada do container e colocá-los onde o Docker (ou o host) garante persistência. Existem três mecanismos para isso — bind mount, volume nomeado e tmpfs — e escolher errado custa caro. Este post explica os três, quando usar cada um, a pegadinha de permissão que pega todo mundo e como fazer backup de um volume.

Por que o filesystem do container é efêmero

Uma image é um conjunto de camadas read-only. Quando você dá docker run, o Docker empilha uma camada gravável em cima — a container layer. Tudo que o processo escreve no filesystem vai para essa camada via copy-on-write. Ela existe enquanto o container existe. docker stop mantém a camada (o container só está parado), mas docker rm a apaga, e docker rm -f faz as duas coisas de uma vez.

O problema prático aparece em qualquer fluxo de redeploy. Subir uma image nova é, na prática, criar um container novo a partir de uma image — container layer nova, dados antigos perdidos. Por isso nada que precise sobreviver a um redeploy pode morar na camada do container. Dados de banco, em especial, são o caso clássico: o Postgres grava em /var/lib/postgresql/data, e se esse caminho estiver na container layer, um docker rm apaga o banco inteiro.

Bind mount: mapeie um caminho do host

Um bind mount conecta um diretório (ou arquivo) do host diretamente a um caminho dentro do container. O Docker não gerencia nada — é o filesystem do host aparecendo lá dentro.

docker run -v /home/rafael/app:/app node:20

Tudo que estiver em /home/rafael/app no host aparece em /app no container, e escritas dos dois lados se enxergam na hora. É isso que faz o bind mount ser a ferramenta certa para hot-reload de código em desenvolvimento: você edita o arquivo no seu editor, o container vê a mudança instantaneamente, o nodemon/vite recarrega. Sem rebuild, sem copy.

O custo é o acoplamento. O bind mount depende de um caminho absoluto existir no host, então o mesmo compose não roda igual na sua máquina e na do colega sem combinar a estrutura de pastas. E vem a pegadinha de permissão: o processo dentro do container roda com um UID (muitas vezes root, UID 0, ou um UID de app como 1000), e os arquivos no bind mount pertencem ao dono no host. Quando os UIDs não batem, você toma permission denied ao escrever, ou pior, arquivos criados pelo container ficam com dono root na sua máquina e você precisa de sudo para apagar. Em dev isso é gerenciável; em produção, é fonte de dor de cabeça.

Volume nomeado: a escolha certa para dados de verdade

Um volume nomeado é gerenciado pelo Docker. Ele vive numa área controlada pelo daemon (em Linux, sob /var/lib/docker/volumes/), e você o referencia por nome, não por caminho do host.

docker volume create pgdata
docker run -v pgdata:/var/lib/postgresql/data postgres:16

No Compose, fica explícito e portável:

services:
  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

Esse é o caminho certo para qualquer dado que precise persistir: Postgres, MySQL, Redis com persistência, uploads de usuário. O volume sobrevive a docker rm do container — você destrói e recria o container à vontade, e os dados continuam lá, anexados pelo nome. É portável (não depende de um caminho do host específico), o Docker cuida das permissões iniciais melhor que um bind mount, e dá para fazer backup com comandos padrão. Se você está em dúvida entre bind mount e volume nomeado para dados, a regra é simples: dados de verdade vão em volume nomeado.

Quando os volumes ficam aninhados num compose grande, junto de redes e configs, a indentação do YAML esconde erros sutis — uma chave volumes: no nível errado e o Docker monta em outro lugar. Para conferir como os volumes estão declarados, converter o compose de YAML para JSON deixa o aninhamento explícito e fácil de auditar.

tmpfs: em memória, some ao parar

Um mount tmpfs vive na RAM do host, nunca toca o disco e desaparece quando o container para. Serve para dados sensíveis ou temporários que você não quer persistidos em lugar nenhum.

docker run --tmpfs /run/secrets:size=64m alpine

Os casos de uso são segredos descriptografados em runtime, caches descartáveis e arquivos temporários de processamento. Como nada vai para o disco, não há risco de um segredo vazar para uma image layer ou para um volume esquecido. A contrapartida é óbvia: nada sobrevive a docker stop, e o conteúdo consome RAM. Use com parcimônia e sempre com size definido.

Permissões, UID e o erro mais comum

A maior parte dos problemas com volumes não é de configuração do Docker — é de UID. O processo no container tem um UID; os arquivos no volume ou bind mount têm um dono. Quando não batem, dá permission denied.

Com volume nomeado, o Docker normalmente inicializa o volume com as permissões corretas na primeira montagem (a image do Postgres, por exemplo, faz chown no diretório de dados). Com bind mount, o dono é quem já existe no host, e o Docker não mexe nisso. A correção que funciona é alinhar os UIDs: rode o container com o mesmo UID do dono dos arquivos (docker run --user 1000:1000), ou ajuste o dono no host para o UID que o processo do container usa. Evite o atalho de rodar tudo como root só para o erro sumir — você troca um problema de permissão por um problema de segurança.

Backup de volume

Volume nomeado é backupável com um container descartável que monta o volume e empacota o conteúdo:

docker run --rm -v pgdata:/data -v $(pwd):/backup alpine \
  tar czf /backup/pgdata.tar.gz -C /data .

O container alpine sobe só para rodar o tar, escreve o .tar.gz no diretório atual via um segundo bind mount e some (--rm). Para restaurar, inverte: monta o volume de destino e extrai o tarball dentro. Para bancos, vale lembrar que um tar dos arquivos vivos pode pegar um estado inconsistente — para Postgres, prefira pg_dump por cima, ou pare o container antes de tarar.

Se você ainda está montando o ambiente de dev e quer ver os volumes no contexto de um stack completo, o post sobre Docker Compose para desenvolvimento mostra como amarrar serviços, redes e volumes num único arquivo.

Perguntas frequentes

Bind mount ou volume nomeado para o banco de dados?

Volume nomeado, sempre. Bind mount acopla os dados a um caminho do host e te expõe a problemas de permissão e portabilidade. O volume nomeado é gerenciado pelo Docker, sobrevive a docker rm, é portável entre máquinas e tem comandos padrão de backup. Bind mount fica para código em dev.

Os dados somem quando eu paro o container?

Depende de onde estão. Se estão na container layer, somem no docker rm (mas não no docker stop — o container parado mantém a camada). Se estão num volume nomeado ou bind mount, sobrevivem a stop e a rm. Se estão num tmpfs, somem assim que o container para, porque vivem só na RAM.

Por que tomo "permission denied" ao escrever no volume?

Quase sempre é descompasso de UID: o processo dentro do container roda com um UID que não tem permissão sobre os arquivos do volume ou bind mount. Alinhe os UIDs com --user no container ou ajustando o dono dos arquivos no host. Rodar como root esconde o sintoma mas cria um buraco de segurança.

Como faço backup de um volume nomeado?

Suba um container descartável que monte o volume e um diretório de backup do host, e rode tar lá dentro. O volume vira /data, o destino vira /backup, o tar empacota e o container some com --rm. Para bancos, um dump lógico (pg_dump) é mais seguro que um tar dos arquivos vivos.

O que levar daqui

Nunca confie no filesystem do container para nada que precise sobreviver a um redeploy — ele é efêmero por design e some no docker rm. Use bind mount para código em desenvolvimento, onde o hot-reload compensa o acoplamento ao host. Use volume nomeado para dados de verdade: banco, uploads, qualquer coisa persistente. Reserve tmpfs para segredos e temporários que devem morrer com o container. E quando bater permission denied, olhe primeiro o UID, não a configuração do Docker.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil