Deploy em produção com Docker Compose: 7 estratégias essenciais de health checks a atualizações zero-downtime

DevOps

"Funciona na minha máquina" — O cemitério de contêineres em produção

O mantra de todo desenvolvedor: "Funciona na minha máquina." Mas quando os contêineres chegam à produção, o verdadeiro pesadelo começa:

  • Contêineres silenciosamente OOM Killed, deixando apenas Out of memory nos logs
  • Banco de dados ainda não pronto, contêineres da aplicação gritando Connection refused
  • Contêiner falha às 3 da manhã sem política de reinício — serviço fora do ar até de manhã
  • Arquivos de log enchendo discos, docker logs exibindo dezenas de GB de texto não estruturado
  • Credenciais de produção em texto plano dentro do docker-compose.yml, senhas de banco de dados expostas

Se você ainda usa docker compose up -d como toda a sua estratégia de produção, este artigo é para você.

Referência de conceitos essenciais

Conceito Finalidade Configuração chave em produção
Health Check Detectar se um contêiner está realmente pronto healthcheck + depends_on.condition
Limites de recursos Limitar CPU/memória, prevenir consumo excessivo deploy.resources.limits
Política de reinício Reinício automático em saída anormal deploy.restart_policy
Secrets Armazenamento criptografado para dados sensíveis secrets + Docker Secret
Driver de logging Logging estruturado + rotação de logs logging.driver + logging.options
Profiles Inicialização seletiva de serviços por ambiente profiles
Watch Sincronização automática de mudanças de arquivos para contêineres watch (Compose Watch)

5 Desafios de produção

Desafio 1: Ordem de inicialização de contêineres incontrolável

O banco de dados ainda está inicializando enquanto o contêiner da aplicação tenta se conectar, causando falhas de inicialização. depends_on apenas garante a ordem de inicialização, não a prontidão do serviço.

Desafio 2: Expansão ilimitada de recursos

Contêineres sem limites de recursos são como carros sem freios. Um único contêiner com vazamento de memória pode consumir toda a memória do host e derrubar todos os serviços.

Desafio 3: O buraco negro dos logs

O driver de log json-file padrão não faz rotação. Após 3 meses de execução, /var/lib/docker enche o disco e todos os serviços falham.

Desafio 4: Exposição de dados sensíveis

Senhas de banco de dados em blocos environment em texto plano, arquivos .env commitados no Git, chaves de API hardcoded em imagens — essas são bombas relógio para incidentes de produção.

Desafio 5: Atualizações significam tempo de inatividade

docker compose up -d para os contêineres antigos antes de iniciar os novos por padrão, tornando o serviço indisponível durante as atualizações. Para serviços 24/7, isso é inaceitável.

7 Padrões de produção

Padrão 1: Health checks e ordenação de dependências

Problema: depends_on apenas controla a ordem de inicialização, não garante a prontidão do serviço.

Solução: Usar healthcheck + depends_on.condition: service_healthy.

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  app:
    image: myapp:latest
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    ports:
      - "3000:3000"

Parâmetros chave:

  • interval: Intervalo de verificação, recomendado 5-10 segundos para produção
  • timeout: Timeout de uma única verificação, recomendado 3-5 segundos
  • retries: Falhas consecutivas antes de marcar como unhealthy
  • start_period: Período de carência após o início do contêiner, falhas não contam para os retries

Padrão 2: Limites de recursos e proteção OOM

Problema: Contêineres sem limites competem pelos recursos do host. Um contêiner descontrolado pode derrubar toda a máquina.

Solução: Usar deploy.resources para definir limites e reservas de CPU e memória.

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
        window: 120s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  worker:
    image: myworker:latest
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G
        reservations:
          memory: 512M

Limites vs Reservas:

  • limits: Teto rígido, se excedido → OOM Kill ou throttling de CPU
  • reservations: Garantia flexível, o escalonador tenta atender mas não impõe

Estratégia de proteção OOM:

services:
  critical-service:
    image: critical-app:latest
    deploy:
      resources:
        limits:
          memory: 1G
    cap_add:
      - SYS_PTRACE

No nível do host, configure vm.overcommit_memory e ajuste as políticas OOM:

# Verificar OOM Score do contêiner
docker inspect --format='{{.State.OOMKilled}}' <container_id>

# Definir política OOM do host: não matar processos críticos
echo -1000 > /proc/<pid>/oom_score_adj

Padrão 3: Logging estruturado e rotação de logs

Problema: O driver de log json-file padrão não faz rotação; discos enchem com o tempo.

Solução: Configurar driver de log + política de rotação. O driver local é recomendado para produção.

services:
  app:
    image: myapp:latest
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"
        tag: "{{.Name}}/{{.ID}}"

  nginx:
    image: nginx:1.27-alpine
    logging:
      driver: json-file
      options:
        max-size: "50m"
        max-file: "10"
        tag: "nginx/{{.Name}}"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

Comparação de drivers de log:

Driver Caso de uso Vantagens Desvantagens
local Padrão para produção Auto-rotação, armazenamento comprimido Apenas local
json-file Logs JSON estruturados necessários Suporte nativo do Docker Necessita configuração manual de rotação
syslog Coleta centralizada de logs Pode enviar para remoto Configuração complexa
fluentd Integração com stack EFK Roteamento flexível de logs Requer implantação do Fluentd

Logging estruturado em nível de aplicação:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .

ENV TZ=UTC
ENV LOG_FORMAT=json

CMD ["node", "server.js"]
const logger = {
  info: (msg, meta = {}) => {
    console.log(JSON.stringify({ level: 'info', msg, ts: new Date().toISOString(), ...meta }));
  },
  error: (msg, meta = {}) => {
    console.error(JSON.stringify({ level: 'error', msg, ts: new Date().toISOString(), ...meta }));
  }
};

Padrão 4: Gerenciamento de Secrets

Problema: Dados sensíveis armazenados em texto plano em arquivos compose ou variáveis de ambiente.

Solução: Docker Secrets + variáveis de ambiente com sufixo _FILE.

secrets:
  db_password:
    file: ./secrets/db_password.txt
  api_key:
    file: ./secrets/api_key.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt

services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

  app:
    image: myapp:latest
    environment:
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
      API_KEY_FILE: /run/secrets/api_key
      JWT_SECRET_FILE: /run/secrets/jwt_secret
    secrets:
      - api_key
      - jwt_secret

Gerenciamento de arquivos de secrets:

# Criar diretório de secrets
mkdir -p secrets
chmod 700 secrets

# Escrever arquivos de secrets
echo "my-super-secret-password-2026" > secrets/db_password.txt
echo "ak-live-xxxx-yyyy-zzzz" > secrets/api_key.txt
echo "jwt-hs256-secret-key-here" > secrets/jwt_secret.txt

# Definir permissões: legível apenas pelo root
chmod 600 secrets/*.txt

.gitignore deve incluir:

secrets/
*.secret
.env.production
.env.staging

Comparação Docker Secrets vs .env:

Recurso Docker Secrets Arquivos .env
Armazenamento criptografado Sim (no modo Swarm) Não
Permissões de arquivo Restritas (/run/secrets/) Depende do filesystem
Trilha de auditoria Sim Não
Sincronização entre nós Swarm sincroniza automaticamente Distribuição manual
Caso de uso Swarm/produção de host único Desenvolvimento

Padrão 5: Atualizações rolling zero-downtime

Problema: docker compose up -d para os contêineres antigos antes de iniciar os novos por padrão.

Solução: Usar docker compose up --no-down + health checks + proxy reverso.

services:
  app:
    image: myapp:${APP_VERSION:-latest}
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
        failure_action: rollback
      rollback_config:
        parallelism: 0
        order: stop-first
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 15s
    labels:
      - "com.toolsku.app=true"
    ports:
      - "3000-3001:3000"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 10s
      timeout: 3s
      retries: 3

Configuração de proxy reverso Nginx:

upstream app_backend {
    server app:3000;
}

server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://app_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_next_upstream error timeout http_502 http_503;
    }

    location /health {
        access_log off;
        return 200 'ok';
        add_header Content-Type text/plain;
    }
}

Script de atualização zero-downtime:

#!/bin/bash
set -euo pipefail

NEW_IMAGE="myapp:v2.0.0"
echo "🚀 Iniciando atualização zero-downtime para ${NEW_IMAGE}"

# 1. Baixar nova imagem
docker compose pull app

# 2. Iniciar novos contêineres (sem parar os antigos)
docker compose up -d --no-deps --scale app=2 app

# 3. Aguardar novos contêineres ficarem saudáveis
echo "⏳ Aguardando novos contêineres ficarem saudáveis..."
sleep 15

# 4. Verificar saúde do novo contêiner
for i in $(seq 1 30); do
  if curl -sf http://localhost:3000/health > /dev/null 2>&1; then
    echo "✅ Novo contêiner está saudável"
    break
  fi
  if [ $i -eq 30 ]; then
    echo "❌ Health check falhou, revertendo..."
    docker compose up -d --no-deps --scale app=1 app
    exit 1
  fi
  sleep 2
done

# 5. Reduzir para 1 réplica
docker compose up -d --no-deps --scale app=1 app

echo "🎉 Atualização concluída com sucesso"

Padrão 6: Stack de monitoramento Prometheus + Grafana

Problema: Executar produção sem monitoramento é voar às cegas — você só fica sabendo dos problemas pelas reclamações dos usuários.

Solução: Implantar um stack completo de monitoramento Prometheus + Grafana.

services:
  prometheus:
    image: prom/prometheus:v2.52.0
    container_name: prometheus
    restart: unless-stopped
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=30d'
      - '--storage.tsdb.retention.size=5GB'
    deploy:
      resources:
        limits:
          memory: 512M
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3001:3000"
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD_FILE: /run/secrets/grafana_password
    secrets:
      - grafana_password
    volumes:
      - grafana_data:/var/lib/grafana
    deploy:
      resources:
        limits:
          memory: 256M
    depends_on:
      - prometheus
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:v1.8.0
    container_name: node-exporter
    restart: unless-stopped
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--path.rootfs=/rootfs'
    networks:
      - monitoring

  alertmanager:
    image: prom/alertmanager:v0.27.0
    container_name: alertmanager
    restart: unless-stopped
    ports:
      - "9093:9093"
    volumes:
      - ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
    networks:
      - monitoring

secrets:
  grafana_password:
    file: ./secrets/grafana_password.txt

volumes:
  prometheus_data:
  grafana_data:

networks:
  monitoring:
    driver: bridge

Configuração do Prometheus:

# monitoring/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "alert_rules.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']

  - job_name: 'node-exporter'
    static_configs:
      - targets: ['node-exporter:9100']

  - job_name: 'app'
    static_configs:
      - targets: ['app:3000']
    metrics_path: /metrics

Regras de alertas:

# monitoring/alert_rules.yml
groups:
  - name: container_alerts
    rules:
      - alert: ContainerDown
        expr: up == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Container {{ $labels.instance }} is down"

      - alert: HighMemoryUsage
        expr: process_resident_memory_bytes / (1024 * 1024) > 400
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Memory usage exceeds 400MB on {{ $labels.instance }}"

      - alert: DiskSpaceLow
        expr: node_filesystem_avail_bytes / node_filesystem_size_bytes < 0.1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Disk space below 10% on {{ $labels.instance }}"

Padrão 7: Configuração multi-ambiente (dev/staging/prod)

Problema: Configs de dev, staging e produção estão misturadas — alterar a config de um ambiente corre o risco de afetar outros.

Solução: Usar docker-compose.override.yml + estratégia de sobreposição multi-arquivo.

Estrutura de diretórios:

project/
├── docker-compose.yml              # Configuração base
├── docker-compose.override.yml     # Sobrescrita de dev (carregada automaticamente)
├── docker-compose.staging.yml      # Sobrescrita de staging
├── docker-compose.prod.yml         # Sobrescrita de produção
├── .env                            # Variáveis de ambiente padrão
├── .env.staging                    # Variáveis de ambiente de staging
├── .env.prod                       # Variáveis de ambiente de produção
├── monitoring/
│   ├── prometheus.yml
│   └── alertmanager.yml
└── secrets/
    ├── db_password.txt
    ├── api_key.txt
    └── grafana_password.txt

Configuração base docker-compose.yml:

services:
  app:
    image: myapp:${APP_VERSION:-latest}
    environment:
      NODE_ENV: ${NODE_ENV:-development}
      DATABASE_URL: postgresql://appuser:${DB_PASSWORD}@postgres:5432/appdb
      REDIS_URL: redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: appdb
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    networks:
      - app-network

secrets:
  db_password:
    file: ./secrets/db_password.txt

volumes:
  postgres_data:

networks:
  app-network:
    driver: bridge

Sobrescrita de dev docker-compose.override.yml (carregada automaticamente):

services:
  app:
    build: .
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
      - "9229:9229"
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
    deploy:
      resources:
        limits:
          memory: 512M
    command: node --inspect=0.0.0.0:9229 server.js

  adminer:
    image: adminer:latest
    ports:
      - "8080:8080"
    networks:
      - app-network

Sobrescrita de produção docker-compose.prod.yml:

services:
  app:
    image: myapp:${APP_VERSION}
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
      LOG_LEVEL: info
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 15s

  postgres:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          memory: 1G
    logging:
      driver: local
      options:
        max-size: "50m"
        max-file: "10"

  redis:
    deploy:
      resources:
        limits:
          memory: 512M
    command: redis-server --appendonly yes --maxmemory 400mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:1.27-alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.prod.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    deploy:
      resources:
        limits:
          memory: 128M
    logging:
      driver: local
      options:
        max-size: "50m"
        max-file: "10"

volumes:
  redis_data:

Comandos de inicialização:

# Desenvolvimento (carrega override automaticamente)
docker compose up -d

# Staging
docker compose -f docker-compose.yml -f docker-compose.staging.yml --env-file .env.staging up -d

# Produção
docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.prod up -d

5 Armadilhas comuns

Armadilha 1: depends_on não significa serviço pronto

Errado:

services:
  app:
    depends_on:
      - postgres
    # Contêiner postgres iniciou, mas o BD pode não estar inicializado ainda

Correto:

services:
  app:
    depends_on:
      postgres:
        condition: service_healthy
  postgres:
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
      interval: 5s
      timeout: 3s
      retries: 5
      start_period: 10s

Armadilha 2: Sem limites de recursos

Errado:

services:
  app:
    image: myapp:latest
    # Sem limites de recursos — um vazamento de memória pode consumir todo o host

Correto:

services:
  app:
    image: myapp:latest
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

Armadilha 3: Sem rotação de logs

Errado:

services:
  app:
    image: myapp:latest
    # Driver json-file padrão, logs crescem indefinidamente

Correto:

services:
  app:
    image: myapp:latest
    logging:
      driver: local
      options:
        max-size: "10m"
        max-file: "5"

Armadilha 4: Dados sensíveis em texto plano

Errado:

services:
  postgres:
    environment:
      POSTGRES_PASSWORD: "my-secret-password-123"
      # Senha em texto plano no arquivo compose — desastre se commitado no Git

Correto:

services:
  postgres:
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Armadilha 5: Usar a tag latest

Errado:

services:
  app:
    image: myapp:latest
    # Cada pull pode obter uma imagem diferente — não é reprodutível

Correto:

services:
  app:
    image: myapp:2.1.0
    # Ou usar uma variável para controle de versão
    image: myapp:${APP_VERSION:-2.1.0}

Referência de solução de erros

Mensagem de erro Causa Solução
OOMKilled Contêiner excedeu o limite de memória Aumentar limite memory ou otimizar uso de memória da app
Connection refused Serviço dependente não pronto Adicionar healthcheck + depends_on.condition
no space left on device Logs/imagens enchendo o disco Configurar rotação logging + docker system prune
Loop de restarting App falha na inicialização Verificar docker logs <id> e confirmar configuração
permission denied Problema de permissão de arquivo/diretório Verificar diretiva user e permissões de volumes
port is already allocated Conflito de portas Alterar mapeamento de portas ou parar processo em conflito
Status unhealthy Health check falhando Verificar se o comando healthcheck está correto
secret not found Arquivo de secret ausente Garantir que o arquivo correspondente existe em secrets/
Cannot connect to the Docker daemon Docker não está em execução systemctl start docker
image pulling failed Falha ao baixar imagem Verificar rede/autenticação do registry/grafia do nome da imagem

Otimização avançada

Docker Compose Watch para desenvolvimento

Compose Watch sincroniza automaticamente mudanças de arquivos para os contêineres sem reconstruir imagens:

services:
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
        - action: sync+restart
          path: ./config
          target: /app/config
# Iniciar modo watch
docker compose watch

Isolamento de rede e segurança

services:
  app:
    networks:
      - frontend
      - backend

  postgres:
    networks:
      - backend
    # postgres não está na rede frontend — acesso externo bloqueado

  nginx:
    networks:
      - frontend
    ports:
      - "80:80"

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true
    # internal:true desabilita acesso externo

Otimização de imagens com multi-stage builds

# ---- Estágio de build ----
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ---- Estágio de runtime ----
FROM node:20-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && \
    adduser -S appuser -u 1001 -G appgroup

COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./

USER appuser
EXPOSE 3000

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "dist/server.js"]

Comparação de ferramentas de orquestração

Recurso Docker Compose Kubernetes Nomad Docker Swarm
Complexidade Baixa Alta Média Baixa
Deploy de host único ✅ Excelente ❌ Exagero ⚠️ Usável ✅ Excelente
Orquestração multi-host ❌ Não suportado ✅ Capacidade central ✅ Capacidade central ⚠️ Básico
Auto-scaling ✅ HPA/VPA ⚠️ Manual
Atualizações rolling ⚠️ Requer scripts ✅ Nativo ✅ Nativo ✅ Nativo
Service discovery ⚠️ DNS ✅ CoreDNS ✅ Consul ✅ DNS
Orquestração de armazenamento ✅ CSI ✅ CSI ⚠️ Básico
Curva de aprendizado Baixa Alta Média Baixa
Escala adequada 1-10 serviços 100+ serviços 50+ serviços 10-50 serviços
Pronto para produção ✅ Host único ✅ Grande escala ✅ Média a grande escala ⚠️ Comunidade em declínio

Recomendação: Use Docker Compose para produção de host único/pequena escala, Kubernetes para grande escala, Nomad se você está no ecossistema HashiCorp. Docker Swarm está cada vez mais marginalizado — não recomendado para novos projetos.

Resumo

O deploy em produção com Docker Compose não é apenas docker compose up -d. Health checks garantem que os serviços estão realmente prontos, limites de recursos previnem avalanches OOM, rotação de logs evita esgotamento de disco, Secrets protegem dados sensíveis, atualizações zero-downtime garantem disponibilidade 24/7, stacks de monitoramento eliminam pontos cegos, e configurações multi-ambiente mantêm dev/staging/prod devidamente separados. Domine essas 7 estratégias, e Docker Compose é totalmente capaz de deploys de produção em pequena e média escala.

Ferramentas recomendadas

  • Formatador JSON - Formatar configurações JSON relacionadas ao Docker Compose YAML
  • Codificação Base64 - Codificar Secrets e dados de configuração sensíveis
  • Calculadora de Hash - Gerar checksums para arquivos de configuração e garantir consistência nos deploys

Experimente estas ferramentas executadas localmente no navegador — nenhum cadastro necessário →

#Docker#Docker Compose#生产部署#容器编排#2026#DevOps