Conteúdo
Tutorial de Docker Volumes: persistência de dados com Volumes e Bind Mount
Os arquivos gravados dentro do contêiner ficam na camada gravável do contêiner. Quando você remove o contêiner, os dados desaparecem junto com ele. Dados que precisam ser preservados, como os de um banco de dados e arquivos enviados por usuários, devem ficar fora do contêiner. O Docker oferece três formas de montagem:
| Tipo | Onde os dados ficam | Indicado para |
|---|---|---|
| Volume | Área gerenciada pelo Docker | Banco de dados e dados persistentes em produção |
| Bind mount | Pasta do host que você especificar | Sincronizar código durante o desenvolvimento e ler arquivos de configuração do host |
| tmpfs | Memória | Dados temporários que você não quer gravar no disco |
Volume: remova o contêiner, os dados continuam
Vamos verificar na prática com PostgreSQL. Inicie o banco de dados e armazene os dados em um Volume chamado pgdata:
docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
-v pgdata:/var/lib/postgresql significa montar o Volume chamado pgdata em /var/lib/postgresql dentro do contêiner. Se o Volume não existir, o Docker o criará automaticamente.
Crie uma tabela, insira um registro e remova o contêiner. Em seguida, inicie um contêiner totalmente novo usando o mesmo Volume:
$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
119ed37ba5c6dc12a773a32d4ef296523529552f94a2ab1ee56113c1b990d7ae
$ docker ps --filter name=db
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
119ed37ba5c6 postgres:18 "docker-entrypoint.s…" 8 seconds ago Up 8 seconds 5432/tcp db
$ docker exec db psql -U postgres -c "CREATE TABLE notes (id serial, body text); INSERT INTO notes (body) VALUES ('hello volume');"
CREATE TABLE
INSERT 0 1
$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
id | body
----+--------------
1 | hello volume
(1 row)
$ docker rm -f db
db
$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
d94fb2966b3dc5790239b4a48d180aedd193f6fc2c846d6878a7c5fcc450f7cd
$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
id | body
----+--------------
1 | hello volume
(1 row)
O novo contêiner encontrou os dados gravados pelo contêiner anterior: eles ficam no Volume e não dependem do ciclo de vida do contêiner.
Diretório de dados do PostgreSQL 18
Muitos tutoriais antigos usam
-v pgdata:/var/lib/postgresql/data. Esse formato era usado até o PostgreSQL 17 e faz opostgres:18falhar ao iniciar:
$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:18
c1cec1074a4d440c1f6d106d994171d290cd8ea9da3266d798c0c81662bd8281
$ docker ps -a --filter name=db
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c1cec1074a4d postgres:18 "docker-entrypoint.s…" 8 seconds ago Exited (1) 7 seconds ago db
$ docker logs db
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" (specifically, using
major-version-specific directory names). This better reflects how
PostgreSQL itself works, and how upgrades are to be performed.
See also https://github.com/docker-library/postgres/pull/1259
Counter to that, there appears to be PostgreSQL data in:
/var/lib/postgresql/data (unused mount/volume)
This is usually the result of upgrading the Docker image without
upgrading the underlying database using "pg_upgrade" (which requires both
versions).
The suggested container configuration for 18+ is to place a single mount
at /var/lib/postgresql which will then place PostgreSQL data in a
subdirectory, allowing usage of "pg_upgrade --link" without mount point
boundary issues.
See https://github.com/docker-library/postgres/issues/37 for a (long)
discussion around this process, and suggestions for how to do so.
$ docker rm -f db
db
$ docker volume rm pgdata
pgdata
A imagem oficial do PostgreSQL 18 passou a armazenar os dados em um subdiretório separado por versão principal, como /var/lib/postgresql/18/docker, para facilitar futuras atualizações com pg_upgrade. A nova abordagem é montar o diretório um nível acima, /var/lib/postgresql:
$ docker volume ls
DRIVER VOLUME NAME
local 5775c1d90defc3dc23a92cdde10cd62b182d9c774ae43f0a40595fe62131a63c
local pgdata
$ docker volume inspect pgdata
[
{
"CreatedAt": "2026-10-13T00:09:44Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
"Name": "pgdata",
"Options": null,
"Scope": "local"
}
]
$ docker exec db sh -c 'echo $PGDATA; ls /var/lib/postgresql'
/var/lib/postgresql/18/docker
18
PGDATAagora é/var/lib/postgresql/18/docker.Mountpointemdocker volume inspecté o local real do Volume no host Docker. No Docker Desktop, ele fica dentro da máquina virtual Linux do Docker e não pode ser visto diretamente pelo Explorador de Arquivos do Windows nem pelo Finder do macOS. Por isso, use o método de backup apresentado a seguir.
Gerenciar Volumes
docker volume ls # listar todos os Volumes
docker volume inspect pgdata # ver informações detalhadas
docker volume rm pgdata # remover (só é possível quando nenhum contêiner estiver usando)
docker volume prune # remover Volumes anônimos não utilizados
docker volume prune -a # remover também Volumes nomeados (cuidado!)
docker volume prune -aedocker system prune --volumesremovem os dados do banco de dados. Confira antes de executar ou faça um backup.
De onde vêm os volumes anônimos
Mesmo sem especificar -v, muitas imagens de banco de dados ainda criam volumes, porque seus Dockerfiles têm uma instrução VOLUME:
$ docker volume ls
DRIVER VOLUME NAME
$ docker run -d --name anon -e POSTGRES_PASSWORD=secret postgres:18
1db583d1b34d0482cfa458a5fe4a7fc7c1320633bde0339407a0c24fe62ad630
$ docker volume ls
DRIVER VOLUME NAME
local 0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8
$ docker inspect --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' anon
volume 0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8 -> /var/lib/postgresql
$ docker rm -f anon
anon
$ docker volume ls
DRIVER VOLUME NAME
local 0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8
$ docker volume prune -f
Deleted Volumes:
0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8
Total reclaimed space: 40.49MB
- Basta executar
docker runpara quedocker volume lsmostre um volume anônimo com um nome que parece uma longa sequência aleatória, montado em/var/lib/postgresql. - Depois que
docker rm -fremove o contêiner, o volume anônimo continua lá, ocupando 40 MB. Um dos motivos pelos quais muitas instalações do Docker vão ficando cada vez maiores são esses volumes anônimos esquecidos. docker volume pruneremove os volumes anônimos que não estão em uso. Ao remover o contêiner, você também pode adicionar-v(docker rm -fv anon) para remover junto o volume anônimo.
Recomendamos sempre usar volumes nomeados: assim, fica claro de cara para que servem.
Bind mount: monte uma pasta do host no contêiner
docker run -d --name site -p 8080:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "${PWD}/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "%cd%/site:/usr/share/nginx/html:ro" nginx:1.30

Edite o título de site/index.html no host com seu editor e salve o arquivo. Depois, atualize o navegador:

Esse é o uso mais comum durante o desenvolvimento: você deixa o código no host e o edita com o editor a que está acostumado; o contêiner fica responsável por executá-lo.
Atenção:
- O caminho no host precisa ser absoluto.
$(pwd)é substituído pelo diretório atual (no Windows PowerShell, use${PWD}). - Se você escrever um nome em vez de um caminho (por exemplo,
site:/data), o Docker vai interpretá-lo como o nome de um volume. - No Linux, os arquivos gravados pelo contêiner como root também pertencem ao root no host. Você pode usar
-u $(id -u):$(id -g)para executar o contêiner com a sua identidade. - Para montar como somente leitura, adicione
:ro.
Caminhos inexistentes com -v e --mount
$ ls -d nothere
ls: nothere: No such file or directory
$ docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
total 8
drwxr-xr-x 2 root root 4096 Oct 13 00:09 .
drwxr-xr-x 1 root root 4096 Oct 13 00:09 ..
$ ls -ld nothere
drwxr-xr-x 2 root root 4096 Oct 13 00:09 nothere
$ docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /work/nothere2
Run 'docker run --help' for more information
Comandos equivalentes no Windows (clique na aba para alternar):
docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "${PWD}/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="${PWD}/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "%cd%/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="%cd%/nothere2",dst=/data alpine:3.23 ls /data
- Com
-v, se o caminho no host não existir, o Docker cria automaticamente uma pasta vazia (que pertence ao root). O contêiner inicia normalmente, mas o que foi montado está vazio. É fácil pensar: “montei, mas não tem arquivo nenhum”. - Com
--mount type=bind, se o caminho não existir, o Docker gera um erro na hora, o que facilita perceber que você digitou o caminho errado.
A sintaxe de --mount é mais longa, mas deixa tudo mais claro:
docker run -d \
--mount type=volume,src=pgdata,dst=/var/lib/postgresql \
--mount type=bind,src="$(pwd)/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly \
-e POSTGRES_PASSWORD=secret postgres:18
docker run -d `
--mount type=volume,src=pgdata,dst=/var/lib/postgresql `
--mount type=bind,src="${PWD}/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly `
-e POSTGRES_PASSWORD=secret postgres:18
docker run -d ^
--mount type=volume,src=pgdata,dst=/var/lib/postgresql ^
--mount type=bind,src="%cd%/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly ^
-e POSTGRES_PASSWORD=secret postgres:18
tmpfs: armazenamento na memória
$ docker run --rm --tmpfs /cache:size=64m alpine:3.23 df -h /cache
Filesystem Size Used Available Use% Mounted on
tmpfs 64.0M 0 64.0M 0% /cache
Os dados ficam apenas na memória e desaparecem quando o contêiner para. É ideal para arquivos temporários (somente em contêineres Linux).
Backup and restore a volume
Docker manages the location of a volume. The most generic way to back it up is to start a temporary container and package the volume into a tar file:
$ docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
$ ls -lh pgdata.tar.gz
-rw-r--r-- 1 root root 4.6M Oct 13 00:10 pgdata.tar.gz
$ docker volume create pgdata-restore
pgdata-restore
$ docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
$ docker run --rm -v pgdata-restore:/data alpine:3.23 ls /data/18/docker
PG_VERSION
base
global
pg_commit_ts
pg_dynshmem
… (output trimmed)
$ rm pgdata.tar.gz
Equivalent commands for Windows (select a tab to switch):
docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "${PWD}:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "${PWD}:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "%cd%:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "%cd%:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
- First command: mounts
pgdataas read-only at/dataand the current directory at/backup, then uses tar to package/dataaspgdata.tar.gzand save it in the host's current directory. - To restore, create a new volume and extract the archive into it the same way. In the restored volume, you can see the PostgreSQL data files under
18/docker.
Directly packaging files from a database that is running may produce inconsistent data. The example above backs up the database after it has been stopped. For databases, use their own export tools, such as
pg_dumpormysqldump. See Run PostgreSQL with Docker.
Which one should you use?
- Databases and data that need to be kept long term → Volume
- See code changes immediately during development → Bind mount
- Give a container a host configuration file to read → Bind mount (read-only)
- Temporary data that does not need to be saved → tmpfs or write directly inside the container
Recap
- Use a named volume for databases and other data that needs to be preserved.
- During development, use a Bind mount so the container can use code directly from the host.
-vcreates an empty directory when the host path does not exist;--mountreturns an error.- To back up a volume, use a temporary container with both the volume and a host directory mounted, then package the data with tar.
Exercises
Exercise 1: Create a volume named notes, use a container to write a file, delete the container, then read the file with another container.
Answer
docker volume create notes
docker run --rm -v notes:/data alpine:3.23 sh -c "echo hello > /data/a.txt"
docker run --rm -v notes:/data alpine:3.23 cat /data/a.txt
The output is hello. See docker volume for volume management commands.
Exercise 2: What is the difference between docker run -v data:/app and docker run -v ./data:/app?
Answer
data has no slash, so it is treated as a volume name; ./data is a path, so it is a Bind mount that mounts data from the host's current directory.
Perguntas frequentes
Onde os dados do Volume ficam no computador?
No Linux, em /var/lib/docker/volumes/nome/_data. No Docker Desktop, o Volume fica dentro da máquina virtual Linux; você não consegue vê-lo no Explorador de Arquivos do Windows nem no Finder. Você pode consultá-lo na tela Volumes do Docker Desktop.
Como escrever o caminho do host com -v no Windows?
No PowerShell, use "${PWD}/site:/app"; no CMD, use "%cd%/site:/app". Os comandos deste capítulo incluem abas com as três formas.
As permissões dos arquivos no Bind mount estão incorretas?
No Linux, os arquivos gravados como root pelo contêiner também pertencem a root no host. Use -u $(id -u):$(id -g) para executar o contêiner com sua identidade.
Atualizado 14 de outubro de 2026