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? Persistência: Ao criar ou vincular volumes a containers, você evita a perda de dados quando o container é recriado ou destruído. 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. Escalabilidade: Em um ambiente onde múltiplos containers são executados, volumes fornecem meios de compartilhamento de dados de forma simples. 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. 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) } 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"] Criando e Executando o Container com Bind Mount 1.Construa a imagem: docker build -t go-upload-app:latest . 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 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 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 mante
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?
- Persistência: Ao criar ou vincular volumes a containers, você evita a perda de dados quando o container é recriado ou destruído.
- 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.
- Escalabilidade: Em um ambiente onde múltiplos containers são executados, volumes fornecem meios de compartilhamento de dados de forma simples.
- 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.
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)
}
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"]
Criando e Executando o Container com Bind Mount
1.Construa a imagem:
docker build -t go-upload-app:latest .
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
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
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:
Executando com Docker Compose
Inicie os serviços:
docker compose up -d
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
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
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
Verifique se o arquivo aparece na pasta local uploads/
.
Parar e remover:
docker compose down
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.
What's Your Reaction?