Docker · 45 min

Persista dados com volumes Docker

Pare de perder dados quando um container é removido. Aprenda os três tipos de armazenamento do Docker (volumes nomeados, bind mounts e tmpfs) persistindo um banco PostgreSQL, inspecionando um volume, editando arquivos ao vivo por um bind mount e usando volumes no Compose.

Problema

Você roda um banco de dados num container, insere linhas importantes, depois remove o container para atualizá-lo — e cada linha some. Isso não é um bug: a camada gravável de um container é **efêmera**. Ela nasce e morre com o container. Quando você faz `docker rm` num container, sua camada gravável — e tudo que seu app escreveu ali — é apagada permanentemente. Tudo bem para um servidor web sem estado, mas catastrófico para um banco de dados, arquivos enviados ou qualquer estado que te importe. A solução é guardar os dados **fora** do ciclo de vida do container, num armazenamento que o Docker gerencia à parte: **volumes nomeados** (gerenciados pelo Docker, a escolha preferida), **bind mounts** (uma pasta do seu host, ótima para desenvolvimento) e **tmpfs** (em memória, para dados sensíveis ou descartáveis). Neste lab você vai provar que o problema é real — escrever dados, destruir o container e ver um *novo* container encontrar os mesmos dados ainda lá. Você vai inspecionar onde o Docker guarda um volume no disco, editar um site ao vivo por um bind mount e declarar volumes num `docker-compose.yml`. No fim você vai saber exatamente qual tipo de armazenamento usar e por quê.

Objetivos

Pré-requisitos

O que você vai construir

Você não vai escrever muito código de aplicação neste lab. Em vez disso, vai rodar um banco PostgreSQL
real e um servidor web nginx, e anexar armazenamento que sobrevive ao container. Você vai escrever
dados, destruir o container e ver um container novinho encontrar os dados intactos — a prova de que
volumes funcionam. Depois vai inspecionar um volume no disco, editar um site ao vivo por um bind mount
e declarar armazenamento no Docker Compose.

O modelo mental essencial: a camada do container é efêmera

Todo container ganha uma fina camada gravável por cima de sua imagem somente-leitura. Tudo que o
app escreve — arquivos de banco, uploads, logs — cai ali por padrão. O problema:

Quando você remove o container, essa camada gravável é apagada junto. Os dados somem.

Isso é por design: containers foram feitos para serem descartáveis. Então os dados persistentes precisam
viver fora do container, num armazenamento que o Docker gerencia de forma independente. O Docker te
dá três opções:

Tipo Onde vive Gerenciado por Melhor para
Volume nomeado Área própria do Docker no host (/var/lib/docker/volumes/) Docker Bancos de dados, dados persistentes do app — a escolha padrão
Bind mount Uma pasta específica que você escolhe no host Você Desenvolvimento: editar código/config ao vivo
tmpfs RAM do host, nunca escrito em disco Docker (em memória) Dados sensíveis ou descartáveis (segredos, caches)

Como escolher

Como trabalhar neste lab

Faça os passos em ordem — cada um se apoia no anterior. Rode cada comando você mesmo e leia a saída real.
No caminho você vai coletar as evidências (prova de persistência, volume inspect, edição ao vivo por
bind mount) que precisa para a entrega do Passo 6.

Passos

  1. O problema: a camada gravável morre com o container

    Antes de tocar em volumes, sinta a dor que eles resolvem. Você vai escrever um arquivo dentro de um
    container, remover o container e confirmar que o arquivo sumiu.

    Prove que os dados se perdem

    Rode um container Alpine descartável, escreva um arquivo nele e olhe-o:

    docker run --name temp -d alpine:3.20 sh -c "echo 'dado importante' > /data.txt && sleep 3600"
    docker exec temp cat /data.txt        # imprime: dado importante
    

    O arquivo existe — ele vive na camada gravável do container. Agora destrua o container e tente
    recuperá-lo:

    docker rm -f temp
    docker run --rm alpine:3.20 cat /data.txt   # erro: arquivo não existe
    

    O novo container é uma instância nova da mesma imagem. Ele nunca viu seu data.txt, porque esse
    arquivo vivia só na camada gravável do container removido — que foi apagada junto.

    Os três tipos de armazenamento (e quando usar cada um)

    A resposta do Docker é manter os dados fora do ciclo de vida do container. Há três mecanismos —
    grave-os na memória, porque todo passo seguinte é um deles:

    • Volumes nomeados — totalmente gerenciados pelo Docker, guardados na área própria do Docker no
      host. A forma preferida de persistir dados. Isolados do sistema de arquivos do host, fáceis de fazer
      backup, migrar e compartilhar entre containers. Use para bancos de dados e qualquer dado persistente
      do app.
    • Bind mounts — um arquivo ou diretório do host é montado dentro do container. Menos isolamento,
      mais flexibilidade; edições de qualquer lado são visíveis no outro na hora. Use para desenvolvimento
      — editar código ou config ao vivo.
    • Montagens tmpfs — armazenadas na memória do host, nunca escritas em disco. Efêmeras por definição.
      Use para dados sensíveis que você não quer em disco, ou dados de rascunho que não precisam sobreviver
      a um reinício.

    Checkpoint

    • Onde o data.txt de fato vivia? (Na camada gravável do container temp.)
    • Por que o segundo container não conseguiu lê-lo? (Essa camada foi apagada no docker rm; o novo container tem sua própria camada vazia.)
    • Qual tipo de armazenamento você usaria para um banco PostgreSQL? (Um volume nomeado — persistente e gerenciado pelo Docker.)

    Entregável deste passo: as duas saídas — o cat bem-sucedido dentro do temp, e o erro "arquivo não existe" depois.

  2. Volumes nomeados: prove que os dados sobrevivem à remoção do container

    Agora resolva o problema. Você vai criar um volume nomeado, rodar um banco PostgreSQL que guarda seus
    arquivos ali, adicionar dados, destruir o container e subir um novo container no mesmo volume —
    com seus dados intactos.

    Crie um volume

    docker volume create pgdata
    

    pgdata é um volume nomeado gerenciado pelo Docker. Ele existe independentemente de qualquer container.

    Rode o PostgreSQL usando o volume

    docker run -d --name lab-postgres \
      -e POSTGRES_PASSWORD=minhasenha \
      -v pgdata:/var/lib/postgresql/data \
      postgres:16-alpine
    
    • -v pgdata:/var/lib/postgresql/data monta o volume nomeado pgdata no caminho exato onde o
      PostgreSQL guarda seus arquivos de banco. O formato é NOME_DO_VOLUME:CAMINHO_NO_CONTAINER.
    • Como o lado esquerdo é um nome simples (não um caminho começando com / ou ./), o Docker o trata
      como volume nomeado, não como bind mount.

    Dê alguns segundos para inicializar, então crie uma tabela e insira uma linha:

    docker exec -it lab-postgres psql -U postgres -c \
      "CREATE TABLE alunos (id serial, nome text); INSERT INTO alunos (nome) VALUES ('Ada');"
    docker exec -it lab-postgres psql -U postgres -c "SELECT * FROM alunos;"
    

    Você deve ver uma linha: Ada. Esses dados agora vivem no volume pgdata, não na camada gravável
    do container.

    Destrua o container — mantenha o volume

    docker rm -f lab-postgres
    docker volume ls          # pgdata continua listado
    

    O container sumiu, mas pgdata sobrevive. É esse o ponto todo: remover um container não remove seus
    volumes nomeados
    .

    Suba um NOVO container no mesmo volume

    docker run -d --name lab-postgres-2 \
      -e POSTGRES_PASSWORD=minhasenha \
      -v pgdata:/var/lib/postgresql/data \
      postgres:16-alpine
    docker exec -it lab-postgres-2 psql -U postgres -c "SELECT * FROM alunos;"
    

    A linha Ada ainda está lá — lida por um container completamente diferente. Isso é persistência:
    os dados sobreviveram ao container que os criou.

    Entregável deste passo: a saída de SELECT * FROM alunos; do lab-postgres-2 (um container diferente) mostrando que a linha sobreviveu.

  3. Inspecione um volume: ls, inspect, Mountpoint

    Um volume não é uma caixa-preta. O Docker te dá comandos para listar volumes e ver exatamente onde e
    como cada um é armazenado.

    Liste os volumes disponíveis

    docker volume ls
    

    Isso mostra cada volume, com seu driver (geralmente local) e nome. Você deve ver pgdata
    na lista.

    Inspecione um volume específico

    docker volume inspect pgdata
    

    A saída é JSON. Os campos que importam:

    • Name — o nome do volume (pgdata).
    • Driver — o driver do volume; local para volumes padrão.
    • Mountpoint — o caminho no host onde os dados do volume de fato vivem (ex.:
      /var/lib/docker/volumes/pgdata/_data). Essa é a área gerenciada pelo próprio Docker — em geral
      você não mexe nela diretamente.
    • Labels — quaisquer rótulos atribuídos ao volume.
    • Scope — local (este host) ou global.

    O que o Mountpoint te diz

    O Mountpoint é onde o Docker guarda os bytes. No Docker Desktop (Windows/macOS) esse caminho vive
    dentro da VM Linux do Docker, então você não vai achá-lo no seu explorador de arquivos normal — isso
    é esperado e correto. Inspecionar o Mountpoint é útil para:

    • confirmar onde os dados de um volume estão armazenados,
    • diagnosticar problemas relacionados a armazenamento,
    • reunir informações para backup ou migração (você vai usar isso no Passo 5).

    Um cuidado do manual: editar arquivos diretamente no Mountpoint do host pode corromper dados que um
    container em execução está usando. Deixe o container (ou um comando de backup) ser quem toca neles.

    Entregável deste passo: a saída de docker volume inspect pgdata, com a linha do Mountpoint destacada.

  4. Bind mounts: edite um arquivo no host, veja ao vivo no container

    Um volume nomeado é gerenciado pelo Docker e vive na área própria do Docker. Um bind mount é
    diferente: você aponta o container para uma pasta exata do seu host, e os dois lados veem os
    mesmos arquivos em tempo real. Essa é a ferramenta para desenvolvimento local.

    Crie uma pasta de site no host

    mkdir -p lab-volume/site
    echo "<h1>Versão 1 — servida de um bind mount</h1>" > lab-volume/site/index.html
    

    Sirva com nginx via bind mount

    docker run -d --name site -p 8080:80 \
      -v "$(pwd)/lab-volume/site:/usr/share/nginx/html:ro" \
      nginx:1.27-alpine
    
    • O lado esquerdo do -v agora é um caminho ($(pwd)/lab-volume/site), então o Docker o trata
      como bind mount, não como volume nomeado.
    • Ele é montado na raiz web do nginx, /usr/share/nginx/html.
    • :ro monta somente-leitura dentro do container — o nginx só precisa ler os arquivos. Tirar o
      :ro deixaria o container escrever de volta na sua pasta do host.

    No Windows PowerShell, troque $(pwd) por ${PWD}. No CMD clássico do Windows, use %cd%.

    Abra http://localhost:8080 — você verá "Versão 1".

    Edite no host, atualize no navegador

    Sem tocar no container, edite o arquivo no seu host:

    echo "<h1>Versão 2 — editada ao vivo no host</h1>" > lab-volume/site/index.html
    

    Atualize http://localhost:8080 — agora diz "Versão 2". Você não reconstruiu nem reiniciou o
    container. O bind mount faz o container ler o mesmo arquivo que você acabou de editar no host.

    Volume nomeado vs bind mount — a diferença prática

    Volume nomeado Bind mount
    Localização Área gerenciada pelo Docker (você não escolhe) Um caminho exato do host que você escolhe
    Isolamento Alto — isolado do FS do host Baixo — ele é uma pasta do host
    Melhor para Bancos, dados persistentes, backups Editar código/config ao vivo em dev
    Portabilidade Portátil, fácil de fazer backup Preso ao layout de caminhos deste host

    Regra de bolso: bind mounts para desenvolvimento (você quer editar arquivos), volumes nomeados
    para dados que precisam persistir
    (você quer que o Docker gerencie).

    Entregável deste passo: anote que editar o index.html no host mudou a página de "Versão 1" para "Versão 2" sem reiniciar o container.

  5. Volumes com Docker Compose + tmpfs + backup

    Em projetos reais você não digita longos comandos docker run -v — você declara o armazenamento no
    docker-compose.yml, onde ele fica versionado e reproduzível. Você vai ligar um volume nomeado e um
    bind mount, conhecer o tmpfs e fazer backup de um volume.

    Declare um volume nomeado no Compose

    Em lab-volume/, crie docker-compose.yml:

    services:
      postgres:
        image: postgres:16-alpine
        environment:
          POSTGRES_PASSWORD: minhasenha
        volumes:
          - pgdata:/var/lib/postgresql/data   # volume nomeado (top-level, abaixo)
        ports:
          - "5432:5432"
    
      web:
        image: nginx:1.27-alpine
        volumes:
          - ./site:/usr/share/nginx/html:ro   # bind mount (caminho do host)
        ports:
          - "8080:80"
    
    volumes:
      pgdata:
    

    Dois tipos de armazenamento num arquivo só:

    • pgdata:/var/lib/... — o lado esquerdo é um nome simples, então é um volume nomeado. Ele
      também precisa ser declarado no bloco volumes: de nível superior, que é o que diz ao Compose para
      gerenciá-lo.
    • ./site:/usr/share/... — o lado esquerdo é um caminho (./site), então é um bind mount para
      a pasta que você criou no Passo 4.

    Suba tudo, depois prove a persistência através de um teardown completo:

    docker compose up -d
    # ... crie uma tabela / adicione dados via psql como no Passo 2 ...
    docker compose down          # para E remove os containers
    docker compose up -d         # containers novos, mesmo volume pgdata
    # ... consulte de novo: os dados continuam lá ...
    

    docker compose down remove os containers mas não o volume nomeado, então os dados sobrevivem.
    (Para também apagar volumes você teria que dizer docker compose down -v — um reset útil, e um erro
    de digitação perigoso.)

    tmpfs — para dados sensíveis ou descartáveis

    Quando os dados nunca podem tocar o disco (um segredo, um cache de rascunho), use tmpfs — ele vive
    na RAM e é apagado quando o container para. No Compose:

      web:
        image: nginx:1.27-alpine
        tmpfs:
          - /tmp        # em memória, some quando o container para
    

    Tudo escrito em /tmp existe só enquanto o container roda; pare-o e os dados são descartados. Isso é
    uma vantagem para segredos e caches, não um lugar para nada que você precise guardar.

    Faça backup de um volume

    Como um volume nomeado é só um diretório que o Docker gerencia, você faz backup empacotando (tar) seu
    conteúdo a partir de um container descartável:

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

    Isso monta o pgdata em /data, monta sua pasta atual do host em /backup e escreve
    pgdata-backup.tar.gz no seu host. Para restaurar, você rodaria o inverso: extrair o tar num volume
    novo. Esse truque de tar-através-de-container é a forma padrão de fazer backup e migrar volumes Docker.

    Notas de segurança

    • Bind mounts expõem uma pasta real do host ao container — cuide das permissões de arquivo, e
      prefira :ro quando o container só precisa ler (como no site nginx acima).
    • Dados sensíveis pertencem ao tmpfs (em memória) ou atrás de encriptação apropriada — não num
      bind mount comum em disco.
    • Use volumes separados para serviços separados, a menos que eles realmente precisem compartilhar dados.

    Entregável deste passo: seu docker-compose.yml, e a prova de que os dados sobreviveram a um ciclo docker compose down + up.

  6. Entregue: documente sua prova de persistência

    Prove que você fez os dados sobreviverem ao container capturando a evidência num pequeno repositório.

    Limpe primeiro

    docker rm -f lab-postgres-2 site 2>/dev/null
    docker compose down          # se você tiver Compose rodando
    # mantenha ou remova o volume pgdata como preferir:
    # docker volume rm pgdata
    

    Monte sua entrega

    Crie uma pasta, inicialize o git e escreva um arquivo chamado SUBMISSION.md. Cole as saídas reais
    que coletou — não texto inventado.

    # Docker Lab 5 — Persista dados com volumes
    
    ## 1. O problema (camada efêmera)
    <cole o `cat /data.txt` bem-sucedido dentro do `temp`, depois o erro "arquivo não existe" após o `docker rm`>
    
    ## 2. Prova de persistência (volume nomeado)
    <cole `SELECT * FROM alunos;` do `lab-postgres-2` — um container DIFERENTE — mostrando que a linha sobreviveu>
    
    ## 3. docker volume inspect pgdata
    <cole o JSON; aponte a linha do Mountpoint>
    
    ## 4. Edição ao vivo por bind mount
    Editar `site/index.html` no host mudou a página de "Versão 1" para "Versão 2" sem reiniciar. (sim / não)
    
    ## 5. Compose + persistência através de down/up
    <cole seu docker-compose.yml, e a evidência de que os dados sobreviveram ao `docker compose down` e depois `up -d`>
    
    ## Reflexão (2-3 frases)
    Nas minhas palavras: por que a camada gravável é efêmera, e quando eu escolheria um volume nomeado vs
    um bind mount vs tmpfs.
    

    Entregue

    git init
    git add SUBMISSION.md docker-compose.yml
    git commit -m "Docker lab 5 — persista dados com volumes"
    # faça push para um repo público ou crie um gist, depois entregue essa URL
    

    Entregue a URL do repositório ou gist como seu deliverable do lab.

    Critério de submissão (autoverificação — sem stubs)

    • O Passo 1 mostra o erro real "arquivo não existe" provando que a camada gravável é efêmera
    • A prova de persistência é de um container diferente (lab-postgres-2), não o que escreveu os dados
    • A saída de docker volume inspect está incluída, com o Mountpoint identificado
    • Você confirmou que uma edição no host chegou ao container ao vivo pelo bind mount
    • Seu docker-compose.yml declara tanto um volume nomeado de nível superior quanto um bind mount
    • Você mostrou os dados sobrevivendo a um ciclo docker compose down + up -d
    • Sua reflexão diz corretamente, nas suas palavras, quando usar volume vs bind mount vs tmpfs
    • Todas as saídas são reais de comandos que você rodou — sem placeholder ou texto inventado

    O que vem a seguir

    Parabéns — esse foi o último lab grátis da trilha de Docker. Você já sabe rodar containers,
    construir imagens, usar Compose, conectar containers por redes e persistir dados com volumes: todo o
    núcleo do Docker do dia a dia.

    Daqui a trilha continua com conteúdo premium que te leva à produção:

    • Segurança no Docker — endurecendo imagens e containers, usuários não-root, segredos, scanning.
    • Docker em Produção — observabilidade com Prometheus e Grafana, healthchecks, limites de recursos.
    • Trilha de Kubernetes — orquestrando containers em escala além de um único host.

    Você construiu a fundação; o premium é onde você a leva à produção.