DEV Community

Cover image for Docker Volumes
Rafael Pazini
Rafael Pazini

Posted on

Docker Volumes

Quando falamos em aplicações containerizadas, um ponto essencial é a persistência de dados. Por padrão, quando um container Docker é removido, todos os dados que estavam “dentro” dele se perdem. A solução? Volumes Docker. Eles permitem que os dados sobrevivam aos ciclos de “sobes e desces” dos containers, fornecendo uma camada de isolamento e escalabilidade para qualquer aplicação.


Por que usar Volumes Docker?

  1. Persistência: Ao criar ou vincular volumes a containers, você evita a perda de dados quando o container é recriado ou destruído.
  2. Isolamento: Separar o armazenamento de dados da lógica do container ajuda a manter tudo mais organizado e facilita substituições ou atualizações do aplicativo.
  3. Escalabilidade: Em um ambiente onde múltiplos containers são executados, volumes fornecem meios de compartilhamento de dados de forma simples.
  4. Desenvolvimento Simplificado: Especialmente em bind mounts, você pode editar arquivos localmente e ver as mudanças refletidas no container em tempo real.

Pense no container como um carro alugado — sempre que você troca de carro, perde todos os itens que estavam nele. Um volume seria a mala pessoal que você leva para qualquer lugar, independentemente de qual carro (container) esteja dirigindo.

Docker Volumes Archtecture


Caso de uso 1: Ligando uma pasta Local para subir arquivos no Container

Imagine que você tem uma aplicação Go que recebe uploads de arquivos dos usuários. Vamos criar um mini-exemplo para demonstrar como manter esses uploads persistentes no seu computador local, em vez de perdê-los ao remover o container.

Image Uploader

Este exemplo simples cria um servidor HTTP que permite envio de arquivos e os salva em uma pasta uploads/.

Aqui está o exemplo do nosso handler, caso você queria ver o projeto completo, ele esta disponível no meu github:

func UploadHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        writeJSONError(w, http.StatusMethodNotAllowed, "Método não permitido")
        return
    }

    file, header, err := r.FormFile("file")
    if err != nil {
        writeJSONError(w, http.StatusBadRequest, "Erro ao ler arquivo do formulário")
        return
    }
    defer file.Close()

    // Salva o arquivo chamando o serviço interno
    err = services.SaveUploadedFile(file, header.Filename)
    if err != nil {
        writeJSONError(w, http.StatusInternalServerError, fmt.Sprintf("Erro ao gravar arquivo: %v", err))
        return
    }

    writeJSONSuccess(w, http.StatusOK, "Upload realizado com sucesso!", header.Filename)
}
Enter fullscreen mode Exit fullscreen mode

Dockerfile

Nosso Dockerfile compila o binário e configura o ambiente para executar a aplicação:

# syntax=docker/dockerfile:1
FROM golang:1.23-alpine AS builder
WORKDIR /app

COPY go.mod ./
RUN go mod download

COPY . .
RUN go build -o server ./cmd/image-uploader

FROM alpine:3.21
WORKDIR /app

COPY --from=builder /app/server /app/server
RUN mkdir -p /app/uploads

EXPOSE 8080
CMD ["/app/server"]
Enter fullscreen mode Exit fullscreen mode

Criando e Executando o Container com Bind Mount

1.Construa a imagem:

docker build -t go-upload-app:latest .
Enter fullscreen mode Exit fullscreen mode

2.Execute o container, mapeando a pasta uploads/ do host para dentro do container:

docker run -d \
  --name meu_container_go \
  -p 8080:8080 \
  -v /caminho/no/host/uploads:/app/uploads \
  go-upload-app:latest
Enter fullscreen mode Exit fullscreen mode

Observe o -v /caminho/no/host/uploads:/app/uploads.

  • Esquerda: caminho no seu computador (host).
  • Direita: caminho no container, conforme definido no Dockerfile (/app/uploads).

Agora, qualquer arquivo enviado via /upload será armazenado em ambos os locais: no container e na sua pasta local (uploads/). Mesmo que você remova o container, os arquivos continuarão no seu host.


Usando Volumes Nomeados

Se você não precisa necessariamente da pasta local e prefere que o Docker gerencie os dados em um “volume nomeado”, eis um exemplo simples com PostgreSQL (apenas como ilustração de outro tipo de volume):

docker volume create pg_dados

docker run -d \
  --name meu_postgres \
  -e POSTGRES_PASSWORD=123456 \
  -v pg_dados:/var/lib/postgresql/data \
  postgres:latest
Enter fullscreen mode Exit fullscreen mode

Assim, o volume pg_dados sobrevive aos containers que o utilizam, preservando as informações do banco de dados.


Segurança: criptografando volumes

Caso você trabalhe com dados sensíveis, considere criptografar o sistema de arquivos subjacente ou usar drivers de volume com suporte a criptografia. Você pode:

  • Armazenar volumes em partições criptografadas.
  • Utilizar soluções de armazenamento em nuvem que suportem criptografia em repouso.
  • Configurar drivers especializados (por exemplo, rexray, portworx etc.) que oferecem criptografia integrada.

Pense nos arquivos do seu volume como documentos confidenciais; mantê-los “a céu aberto” não é seguro. Use um “cofre” (criptografia) e proteja-o com senhas (chaves de criptografia).


Exemplo com Docker Compose

Agora que já entendemos como os volumes podem ajudar a manter dados, vamos dar um passo além e orquestrar vários serviços com o Docker Compose. Essa ferramenta facilita muito as coisas ao permitir que a configuração de todos os containers seja feita em um único arquivo.

Imagine que você possui um banco de dados e não quer perder tudo o que foi salvo sempre que o container for desligado. É aqui que os volumes entram: você pode armazenar os dados no seu computador (host), garantindo que eles permaneçam intactos mesmo se o container for removido.

services:
  app:
    build: .
    container_name: go_app_container
    ports:
      - "8080:8080"
    volumes:
      # Faz bind mount: a pasta 'uploads' do host é mapeada para '/app/uploads' dentro do container
      - ./uploads:/app/uploads
    depends_on:
      - db

  db:
    image: postgres:17-alpine
    container_name: postgres_container
    environment:
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
      POSTGRES_DB: mydb
    volumes:
      # Volume nomeado para persistir dados do PostgreSQL
      - db_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  db_data:
Enter fullscreen mode Exit fullscreen mode

Executando com Docker Compose

Inicie os serviços:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

O Compose vai construir a imagem da aplicação Go, baixar a imagem postgres:17-alpine, criar o volume db_data e iniciar os containers.

Verifique se tudo está rodando:

docker compose ps
Enter fullscreen mode Exit fullscreen mode

Você deve ver algo como:

Name                Command             State         Ports
------------------------------------------------------------------
go_app_container    "/app/server"       Up            0.0.0.0:8080->8080/tcp
postgres_container  "docker-entrypoint…"Up            0.0.0.0:5432->5432/tcp
Enter fullscreen mode Exit fullscreen mode

Teste o upload:

Você pode enviar um arquivo usando curl ou outro cliente HTTP:

curl -F "file=@/caminho/do/seu/arquivo.jpg" http://localhost:8080/upload
Enter fullscreen mode Exit fullscreen mode

Verifique se o arquivo aparece na pasta local uploads/.
Parar e remover:

docker compose down
Enter fullscreen mode Exit fullscreen mode

Isso remove os containers, mas o volume db_data permanece. Se você subir novamente, o PostgreSQL retoma de onde parou, pois os dados estão persistidos.

Conclusão

Volumes Docker são a chave para garantir que dados importantes não sejam descartados ao recriar containers. Quer você prefira bind mounts para desenvolvimento local (permitindo rápida iteração) ou volumes nomeados para produção, o uso correto deles traz resiliência e organização à sua aplicação em contêineres.

Ao criar um ambiente com Go e Postgres usando Docker Compose, percebe-se como é simples gerenciar múltiplos serviços e seus respectivos volumes. Isso garante não apenas persistência de dados, mas também escalabilidade e organização arquitetural.

Se você ainda não testou a persistência de dados com volumes em seus projetos, este é um excelente ponto de partida. Adapte o exemplo fornecido, faça seus próprios testes e compartilhe suas experiências — a comunidade de desenvolvedores está sempre pronta para discutir melhorias e resolver eventuais desafios.

Top comments (0)