Volumes: persistindo dados em containers

1. Introdução à persistência de dados em containers

Containers são, por natureza, efêmeros. Quando um container é removido, todos os dados escritos em seu sistema de arquivos são perdidos. Essa característica, embora desejável para garantir imutabilidade e escalabilidade, torna-se um problema crítico quando precisamos armazenar dados de banco de dados, logs de aplicação, uploads de usuários ou qualquer informação que precise sobreviver ao ciclo de vida do container.

Para resolver esse problema, o Docker introduziu o conceito de volumes: mecanismos que permitem persistir dados fora do sistema de arquivos do container, mantendo-os disponíveis mesmo após a remoção e recriação do container.

Existem três tipos principais de montagem no Docker:

  • Volumes gerenciados pelo Docker: armazenados em área gerenciada pelo daemon (/var/lib/docker/volumes/)
  • Bind mounts: mapeamento direto de um diretório do host para dentro do container
  • tmpfs mounts: dados armazenados exclusivamente em memória RAM (voláteis)

2. Tipos de montagem no Docker

Volumes gerenciados pelo Docker

São a forma recomendada para produção. O Docker gerencia todo o ciclo de vida, incluindo backup e migração.

docker volume create meu-volume
docker volume ls
docker volume inspect meu-volume

Bind mounts

Mapeiam diretamente um caminho do host para o container. Úteis em desenvolvimento para compartilhar código-fonte.

docker run -v /host/caminho:/container/caminho nginx

tmpfs mounts

Dados em RAM, perdidos ao parar o container. Ideais para dados sensíveis que não devem ser persistidos em disco.

docker run --tmpfs /app/temp:rw,noexec,nosuid,size=64m nginx

3. Trabalhando com volumes no Docker

Comandos essenciais

# Criar volume
docker volume create dados-mysql

# Listar volumes
docker volume ls

# Inspecionar volume
docker volume inspect dados-mysql

# Remover volume
docker volume rm dados-mysql

Montando volumes: flag -v vs --mount

A flag -v é mais concisa, enquanto --mount é mais explícita e recomendada para scripts de produção.

# Usando -v (modo antigo)
docker run -v dados-mysql:/var/lib/mysql mysql:8.0

# Usando --mount (modo explícito)
docker run --mount source=dados-mysql,target=/var/lib/mysql mysql:8.0

Exemplo prático: persistindo dados do MySQL

# Criar volume para dados do MySQL
docker volume create mysql-data

# Executar container MySQL com volume persistente
docker run -d \
  --name mysql-db \
  --mount source=mysql-data,target=/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=minhasenha \
  mysql:8.0

# Parar e remover o container (dados persistem no volume)
docker stop mysql-db
docker rm mysql-db

# Recriar o container montando o mesmo volume
docker run -d \
  --name mysql-db-novo \
  --mount source=mysql-data,target=/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=minhasenha \
  mysql:8.0

4. Gerenciamento de permissões e ownership

Um problema comum ao usar volumes é o conflito de UID/GID entre o container e o host. O container pode executar processos como mysql (UID 999) enquanto o host espera outro UID.

Boas práticas

# No Dockerfile, definir usuário explícito
FROM alpine:3.18
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

Exemplo: volume compartilhado entre containers

# Criar volume compartilhado
docker volume create shared-data

# Container escritor (UID 1000)
docker run -d --name escritor \
  --mount source=shared-data,target=/dados \
  alpine sh -c "while true; do echo 'dado' >> /dados/arquivo.txt; sleep 5; done"

# Container leitor (UID 1001)
docker run -d --name leitor \
  --mount source=shared-data,target=/dados \
  alpine sh -c "while true; do cat /dados/arquivo.txt; sleep 10; done"

5. Backup e restauração de volumes

Backup de volume para arquivo tar

# Backup do volume mysql-data para arquivo tar
docker run --rm \
  -v mysql-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/mysql-backup-$(date +%Y%m%d).tar.gz -C /data .

Restauração de backup

# Criar volume vazio
docker volume create mysql-data-restaurado

# Restaurar backup para o volume
docker run --rm \
  -v mysql-data-restaurado:/data \
  -v $(pwd):/backup \
  alpine tar xzf /backup/mysql-backup-20240101.tar.gz -C /data

Automação com cron

# Script de backup automático (backup.sh)
#!/bin/bash
docker run --rm \
  -v mysql-data:/data \
  -v /backup:/backup \
  alpine tar czf /backup/mysql-$(date +%Y%m%d-%H%M).tar.gz -C /data .

# Agendar no crontab (executar diariamente às 2h)
0 2 * * * /caminho/backup.sh

6. Volumes no Docker Compose

O Docker Compose simplifica o gerenciamento de volumes em ambientes multi-container.

# docker-compose.yml
version: '3.8'

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - web-data:/usr/share/nginx/html
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: appdb
    volumes:
      - db-data:/var/lib/mysql

volumes:
  web-data:
  db-data:

7. Volumes no Kubernetes (Persistent Volumes)

No Kubernetes, o gerenciamento de armazenamento persistente é feito através de três objetos principais:

  • PersistentVolume (PV): recurso de armazenamento no cluster
  • PersistentVolumeClaim (PVC): solicitação de armazenamento pelo usuário
  • StorageClass: provisionamento dinâmico de PVs

Exemplo: PVC e Pod

# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: standard
# deployment-mysql.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "rootpass"
        volumeMounts:
        - name: mysql-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-storage
        persistentVolumeClaim:
          claimName: mysql-pvc

StatefulSet com volume persistente

Para bancos de dados, recomenda-se usar StatefulSet, que garante identidade única para cada pod e volumes estáveis.

# statefulset-mysql.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 10Gi

8. Boas práticas e considerações finais

  • Produção: prefira volumes gerenciados pelo Docker ou PVs no Kubernetes
  • Desenvolvimento: bind mounts são úteis para hot-reload de código
  • Limpeza: use docker volume prune regularmente para remover volumes órfãos
  • Monitoramento: configure alertas para uso de disco nos volumes
  • Segurança: evite tmpfs para dados que precisam ser recuperados após crash
  • Backup: automatize backups com scripts e ferramentas como Velero no Kubernetes

Lembre-se: dados são o ativo mais valioso de qualquer aplicação. Trate volumes com o mesmo cuidado que trata seus bancos de dados em servidores tradicionais.

Referências