O que Todo Dev Deve Saber sobre Volumes em Docker: bind mounts, named volumes e tmpfs Comparados Já leu

O Problema: Persistência de Dados em Containers Quando você cria um container Docker, todo arquivo criado dentro dele é armazenado em uma camada de leitura-escrita do container. O problema? Quando o container é removido, todos esses dados desaparecem. Isso funciona perfeitamente para aplicações stateless (sem estado), mas se você está rodando um banco de dados, uma aplicação que precisa manter configurações, ou qualquer serviço que dependa de persistência, você tem um grande problema nas mãos. Docker oferece três mecanismos principais para resolver isso: bind mounts, named volumes e tmpfs. Cada um tem seu próprio caso de uso, limitações e características de performance. Entender as diferenças entre eles é fundamental para construir aplicações robustas e eficientes. Ao longo deste artigo, vamos explorar cada um em detalhes, comparar quando usar cada um, e trabalhar com exemplos reais que você pode executar imediatamente. Bind Mounts: Conectando o Host ao Container Conceito Fundamental Um bind mount é basicamente um atalho: você monta um diretório

O Problema: Persistência de Dados em Containers

Quando você cria um container Docker, todo arquivo criado dentro dele é armazenado em uma camada de leitura-escrita do container. O problema? Quando o container é removido, todos esses dados desaparecem. Isso funciona perfeitamente para aplicações stateless (sem estado), mas se você está rodando um banco de dados, uma aplicação que precisa manter configurações, ou qualquer serviço que dependa de persistência, você tem um grande problema nas mãos.

Docker oferece três mecanismos principais para resolver isso: bind mounts, named volumes e tmpfs. Cada um tem seu próprio caso de uso, limitações e características de performance. Entender as diferenças entre eles é fundamental para construir aplicações robustas e eficientes. Ao longo deste artigo, vamos explorar cada um em detalhes, comparar quando usar cada um, e trabalhar com exemplos reais que você pode executar imediatamente.

Bind Mounts: Conectando o Host ao Container

Conceito Fundamental

Um bind mount é basicamente um atalho: você monta um diretório ou arquivo do seu sistema de arquivos host diretamente dentro do container. Qualquer alteração feita no container é refletida no host, e vice-versa. Isso é poderoso para desenvolvimento, porque você pode editar arquivos no seu editor de código preferido no host enquanto o container usa-os em tempo real.

A sintaxe é simples: você especifica um caminho no host e um caminho no container, e Docker cria uma conexão direta entre eles.

Quando Usar Bind Mounts

Use bind mounts quando você está desenvolvendo e quer que as alterações de código no host sejam imediatamente refletidas no container. Eles são perfeitos para desenvolvimento local, porque eliminam a necessidade de reconstruir a imagem a cada mudança. No entanto, evite usar bind mounts para dados críticos de produção, pois eles dependem da estrutura de diretórios do host e não oferecem nenhum isolamento ou gerenciamento automático do Docker.

Exemplo Prático: Desenvolvendo uma Aplicação Node.js

Suponha que você tem uma aplicação Node.js que você quer desenvolver. Você quer editar o código localmente e ver as mudanças refletidas instantaneamente no container:

# Estrutura do seu projeto local
# ./app
# ├── index.js
# ├── package.json
# └── node_modules

# Arquivo: Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
EXPOSE 3000
CMD ["node", "index.js"]
# Arquivo: index.js
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Olá do Docker!' });
});

app.listen(3000, () => {
  console.log('Servidor rodando na porta 3000');
});

Agora, em vez de apenas fazer docker build e docker run, você usa bind mount:

# Construir a imagem
docker build -t minha-app .

# Rodar o container com bind mount
# O caminho ./app no host é montado em /app no container
docker run -it -v $(pwd):/app -p 3000:3000 minha-app

A sintaxe -v $(pwd):/app significa: monte o diretório atual do host (seu projeto local) no diretório /app do container. Agora você pode editar index.js no seu editor favorito, e o container vai usar o arquivo modificado imediatamente (se tiver hot reload configurado).

Limitações de Bind Mounts

Bind mounts têm alguns problemas importantes. Primeiro, eles dependem da estrutura de diretórios específica do seu host, o que significa que o mesmo comando pode funcionar diferente em sistemas diferentes (Windows vs Linux vs macOS). Segundo, o Docker não gerencia esses diretórios — você é responsável pela limpeza. Terceiro, em termos de performance, bind mounts podem ser lentos, especialmente no Docker Desktop para Mac e Windows, porque envolvem overhead de sincronização de arquivos entre o sistema operacional host e a VM do Docker.

Named Volumes: Gerenciamento Pelo Docker

Conceito Fundamental

Um named volume é um volume gerenciado pelo Docker. Em vez de apontar para um diretório específico no host, você dá um nome ao volume, e Docker se encarrega de criar e gerenciar o local exato onde os dados são armazenados (geralmente em /var/lib/docker/volumes/). Esse é o mecanismo recomendado para dados que precisam persistir entre container restarts e deployments.

Quando Usar Named Volumes

Use named volumes quando você precisa que dados persistam além da vida útil do container. Isso inclui bancos de dados, arquivos de configuração que o container modifica, caches de longa duração, ou qualquer coisa que deva ser preservada. Named volumes são a escolha padrão para ambientes de produção porque oferecem melhor performance, compatibilidade cross-platform, e gerenciamento transparente.

Exemplo Prático: PostgreSQL com Persistência

Imagine que você quer rodar um PostgreSQL em um container, e quer que os dados do banco persistam mesmo após parar e remover o container:

# Criar um named volume
docker volume create meu-postgres-data

# Rodar o PostgreSQL com o named volume
docker run -d \
  --name postgres-db \
  -e POSTGRES_PASSWORD=senha123 \
  -e POSTGRES_DB=minha_aplicacao \
  -v meu-postgres-data:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:15-alpine

A sintaxe -v meu-postgres-data:/var/lib/postgresql/data significa: use o named volume chamado meu-postgres-data e monte-o no diretório /var/lib/postgresql/data do container (onde o PostgreSQL armazena seus dados).

Agora, se você parar e remove o container:

docker stop postgres-db
docker rm postgres-db

Os dados continuam no volume. Quando você cria um novo container com o mesmo volume:

docker run -d \
  --name postgres-db \
  -e POSTGRES_PASSWORD=senha123 \
  -e POSTGRES_DB=minha_aplicacao \
  -v meu-postgres-data:/var/lib/postgresql/data \
  -p 5432:5432 \
  postgres:15-alpine

O novo container terá acesso aos dados antigos. É como se o banco de dados nunca tivesse parado.

Vantagens dos Named Volumes

Named volumes oferecem várias vantagens sobre bind mounts. Docker gerencia todo o ciclo de vida do volume, incluindo limpeza (via docker volume prune). Eles funcionam consistentemente em Windows, Mac e Linux, sem problemas de path. A performance é melhor, especialmente em Docker Desktop, porque Docker otimiza a sincronização de dados. Além disso, você pode inspeccionar volumes, fazer backup, ou compartilhá-los entre containers facilmente.

Inspecionando e Gerenciando Volumes

Docker fornece comandos para trabalhar com volumes:

# Listar todos os volumes
docker volume ls

# Inspecionar um volume específico
docker volume inspect meu-postgres-data

# Resultado será algo como:
# [
#   {
#     "CreatedAt": "2024-01-15T10:30:00Z",
#     "Driver": "local",
#     "Labels": {},
#     "Mountpoint": "/var/lib/docker/volumes/meu-postgres-data/_data",
#     "Name": "meu-postgres-data",
#     "Options": {},
#     "Scope": "local"
#   }
# ]

# Remover um volume (cuidado: isso deleta os dados!)
docker volume rm meu-postgres-data

# Remover todos os volumes não utilizados
docker volume prune

tmpfs: Armazenamento Temporário em Memória

Conceito Fundamental

Um tmpfs é um volume que existe apenas na memória RAM do host. Qualquer dado escrito em um tmpfs é perdido quando o container para. Isso pode parecer inútil à primeira vista, mas é extremamente útil para dados temporários que não precisam persistir: caches, arquivos de sessão, sockets Unix, ou qualquer coisa que você quer garantir que será limpa automaticamente.

Quando Usar tmpfs

Use tmpfs quando você precisa de espaço rápido e temporário. Porque os dados estão na RAM, tmpfs é incrivelmente rápido — perfeito para caches de aplicação, arquivos temporários gerados durante processamento, ou dados sensíveis que você quer garantir que serão destruídos quando o container parar. Evite tmpfs para dados que precisam persistir.

Exemplo Prático: Cache em Memória para uma Aplicação Web

Imagine uma aplicação que precisa de um diretório para armazenar arquivos temporários durante processamento:

# Arquivo: Dockerfile
FROM ubuntu:22.04
RUN apt-get update && apt-get install -y python3 python3-pip
WORKDIR /app
COPY requirements.txt ./
RUN pip install -r requirements.txt
COPY app.py ./
EXPOSE 5000
CMD ["python3", "app.py"]
# Arquivo: app.py
from flask import Flask
import os
import tempfile

app = Flask(__name__)

@app.route('/process', methods=['POST'])
def process_file():
    # Usar /tmp para armazenar arquivo temporário durante processamento
    temp_dir = '/tmp/processing'
    os.makedirs(temp_dir, exist_ok=True)

    # Processar arquivo...
    temp_file = os.path.join(temp_dir, 'temp_data.txt')
    with open(temp_file, 'w') as f:
        f.write('dados temporários')

    # Depois de processar, arquivo será automaticamente limpo
    return {'status': 'ok'}

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

Agora, você monta um tmpfs para garantir que o diretório temporário use RAM:

docker build -t minha-app-python .

docker run -d \
  --name app-web \
  --mount type=tmpfs,destination=/tmp,size=256m \
  -p 5000:5000 \
  minha-app-python

A sintaxe --mount type=tmpfs,destination=/tmp,size=256m significa: crie um tmpfs no diretório /tmp do container com limite de 256MB de RAM. Dados escritos lá serão perdidos quando o container parar.

Limitações de tmpfs

O problema óbvio é que os dados são perdidos permanentemente quando o container para — não há recuperação. Além disso, você tem limite de RAM disponível no host, então não é viável armazenar grandes volumes de dados. Finalmente, se o host ficar sem memória, o kernel pode ter comportamentos impredizíveis, dependendo da configuração de swap.

Comparação Prática: Quando Usar Cada Um

Tabela Comparativa

Característica Bind Mount Named Volume tmpfs
Persistência Sim (no host) Sim (gerenciado Docker) Não (perdido ao parar)
Performance Moderada Alta Muito alta
Gerenciamento Manual Automático Automático
Caso de Uso Desenvolvimento Produção Dados temporários
Cross-platform Problemático Excelente Excelente
Segurança Baixa (expõe host) Alta Alta

Cenário Real: Stack Completo com Docker Compose

Para entender melhor quando usar cada um, vamos construir uma aplicação real que usa os três simultaneamente:

# Arquivo: docker-compose.yml
version: '3.9'

services:
  # Banco de dados: usa named volume para persistência
  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_PASSWORD: senha_segura
      POSTGRES_DB: producao
    volumes:
      # Named volume para dados do banco (persistem entre restarts)
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Aplicação web: usa bind mount para desenvolvimento
  app:
    build: ./src
    environment:
      DATABASE_URL: postgresql://postgres:senha_segura@database:5432/producao
      NODE_ENV: development
    volumes:
      # Bind mount para código (desenvolvimento ágil)
      - ./src:/app
      # tmpfs para arquivos temporários (rápido, sem persistência)
      - /app/tmp:/tmp
    ports:
      - "3000:3000"
    depends_on:
      database:
        condition: service_healthy
    command: npm run dev

  # Cache redis: usa tmpfs + named volume híbrido
  cache:
    image: redis:7-alpine
    volumes:
      # Named volume para dump RDB (snapshot do cache)
      - redis-dump:/data
      # tmpfs para working memory (rápido)
      - cache-memory:/dev/shm:size=256m
    ports:
      - "6379:6379"
    command: redis-server --dir /data --dbfilename dump.rdb

volumes:
  postgres-data:
    driver: local
  redis-dump:
    driver: local

# tmpfs é definido via --mount na linha de comando ou no compose, mas
# para simplicidade, usamos volumes regulares aqui

Neste exemplo:

  • database usa um named volume (postgres-data) porque os dados do PostgreSQL precisam persistir entre container restarts
  • app usa um bind mount (./src:/app) porque estamos em desenvolvimento e queremos editar código localmente
  • cache usa um named volume para snapshots persistentes, mas você poderia usar tmpfs para dados puramente em memória

Executando o Stack Completo

# Construir e levantar tudo
docker-compose up -d

# Verificar status
docker-compose ps

# Ver logs
docker-compose logs -f app

# Parar tudo (dados em volumes nomeados e bind mounts persistem)
docker-compose down

# Remover tudo incluindo volumes (CUIDADO: deleta dados!)
docker-compose down -v

Conclusão

Você aprendeu que Docker oferece três mecanismos distintos de persistência, cada um com propósitos diferentes. Bind mounts conectam diretórios do host ao container — excelente para desenvolvimento, mas problemático para produção. Named volumes são gerenciados pelo Docker e são a escolha padrão para dados persistentes em qualquer ambiente. tmpfs armazena dados em RAM e é perfeito para temporários, oferecendo máxima performance com destruição automática.

A chave é entender que não existe uma solução "melhor" universal. Use bind mounts apenas durante desenvolvimento. Use named volumes para qualquer dado que deva persistir. Use tmpfs quando performance é crítica e os dados são descartáveis. Em aplicações reais, você provavelmente usará os três simultaneamente, cada um cumprindo seu papel específico.

Referências


Artigos relacionados