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:

TipoOnde os dados ficamIndicado para
VolumeÁrea gerenciada pelo DockerBanco de dados e dados persistentes em produção
Bind mountPasta do host que você especificarSincronizar código durante o desenvolvimento e ler arquivos de configuração do host
tmpfsMemóriaDados 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 o postgres:18 falhar 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
  • PGDATA agora é /var/lib/postgresql/18/docker.
  • Mountpoint em docker 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 -a e docker system prune --volumes removem 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 run para que docker volume ls mostre um volume anônimo com um nome que parece uma longa sequência aleatória, montado em /var/lib/postgresql.
  • Depois que docker rm -f remove 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 prune remove 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
Página antes da alteração
nginx exibe o conteúdo de site/index.html no host

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

Página depois da alteração
Sem reconstruir a imagem nem reiniciar o contêiner, basta atualizar para ver a alteração

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 pgdata as read-only at /data and the current directory at /backup, then uses tar to package /data as pgdata.tar.gz and 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_dump or mysqldump. 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.
  • -v creates an empty directory when the host path does not exist; --mount returns 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