Pular para o conteúdo
B
Bradata
DevOpsCI/CDDockerKubernetesinfraestrutura

DevOps para aplicações enterprise: CI/CD, Docker, Kubernetes e deploy em produção

Guia prático de DevOps empresarial: CI/CD com GitHub Actions, Docker multi-stage, Kubernetes vs ECS, monitoramento e blue-green deploy.

Por Bradata··12 min de leitura

O deploy manual é o maior risco operacional de software enterprise

Toda equipe de desenvolvimento que ainda faz deploy manual (conecta no servidor via SSH, faz git pull, reinicia o serviço e torce para funcionar) está a um erro humano de derrubar o sistema em produção.

Em sistemas empresariais (ERP, SaaS, plataformas de gestão), onde cada minuto de downtime impacta operações reais de empresas, o deploy manual é inaceitável. A solução é DevOps: cultura, práticas e ferramentas que automatizam o caminho do código até a produção com segurança, velocidade e rastreabilidade.

Este post detalha as práticas de DevOps que aplicamos em projetos enterprise na Bradata, do CI/CD pipeline até monitoramento em produção, com exemplos reais e configurações que funcionam.

CI/CD: o pipeline que garante qualidade a cada commit

CI/CD (Continuous Integration / Continuous Delivery) é o coração do DevOps. Cada commit no repositório aciona uma sequência automatizada de verificações e, se tudo passar, o deploy acontece sem intervenção humana.

Anatomia de um pipeline CI/CD enterprise

Commit → Lint → Testes → Build → Security Scan → Deploy Staging → Testes E2E → Deploy Produção

Cada etapa é um gate: se falhar, o pipeline para e notifica a equipe.

CI/CD com GitHub Actions: configuração real

O GitHub Actions é a ferramenta de CI/CD mais adotada em projetos modernos. Uma configuração real de pipeline enterprise:

name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

env:
  NODE_VERSION: '20'
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  lint-and-test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - name: Lint
        run: npm run lint

      - name: Type Check
        run: npm run type-check

      - name: Unit Tests
        run: npm run test -- --coverage
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/test_db

      - name: Upload Coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Trivy vulnerability scanner
        uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          scan-ref: '.'
          severity: 'CRITICAL,HIGH'

      - name: Audit dependencies
        run: npm audit --audit-level=high

  build-and-push:
    needs: [lint-and-test, security-scan]
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy-staging:
    needs: build-and-push
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        run: |
          # Deploy via kubectl, ECS, ou ferramenta específica
          echo "Deploying ${{ github.sha }} to staging"

  e2e-tests:
    needs: deploy-staging
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run E2E tests against staging
        run: npm run test:e2e
        env:
          BASE_URL: https://staging.app.exemplo.com.br

  deploy-production:
    needs: e2e-tests
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to production
        run: |
          echo "Deploying ${{ github.sha }} to production"

Pontos importantes desse pipeline

  • Serviço PostgreSQL no CI: testes rodam contra um banco real, não mocks. Isso pega bugs de query e migração
  • Security scan com Trivy: vulnerabilidades em dependências e no código são detectadas antes do merge
  • Cache do Docker: cache-from e cache-to com GitHub Actions cache reduzem tempo de build de 8 minutos para 2 minutos
  • Environments no GitHub: staging e production com aprovações manuais opcionais e secrets separados
  • Testes E2E contra staging: antes de ir para produção, testes end-to-end validam o sistema completo

Docker: containerização para consistência de ambientes

Docker garante que o código roda da mesma forma no laptop do desenvolvedor, no CI e em produção. Para aplicações enterprise, o Dockerfile precisa ser otimizado.

Dockerfile multi-stage para Node.js/Next.js

# Stage 1: Dependencies
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production && \
    cp -R node_modules /prod_modules && \
    npm ci

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build

# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/api/health || exit 1

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

Por que multi-stage importa

AspectoSingle-stageMulti-stage
Tamanho da imagem1.2 GB+150-250 MB
Superfície de ataquedevDependencies inclusasSó produção
Tempo de pull45-60s8-15s
Custo de registryAltoBaixo
Startup timeLentoRápido

Docker Compose para desenvolvimento local

version: '3.8'
services:
  app:
    build:
      context: .
      target: deps  # Usa o stage de deps para dev
    volumes:
      - .:/app
      - /app/node_modules
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgresql://dev:dev@postgres:5432/app_dev
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    command: npm run dev

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: app_dev
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: dev
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U dev"]
      interval: 5s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  pgdata:

Kubernetes vs ECS: onde rodar containers em produção

Depois de containerizar a aplicação, a pergunta é: onde orquestrar os containers em produção?

Amazon ECS (Elastic Container Service)

ECS é a solução de orquestração da AWS. Mais simples que Kubernetes, ideal para equipes menores:

Vantagens:

  • Integração nativa com todo ecossistema AWS (ALB, RDS, CloudWatch, IAM)
  • Fargate elimina gerenciamento de servidores (serverless containers)
  • Menor curva de aprendizado que Kubernetes
  • Custo previsível com Fargate (paga por vCPU e memória usados)

Limitações:

  • Vendor lock-in com AWS
  • Menos flexível que Kubernetes para workloads complexos
  • Comunidade menor, menos tooling open-source

Quando usar: equipes de 2-10 devs, aplicações com até 10-15 serviços, quando já está no ecossistema AWS.

Kubernetes (K8s)

Kubernetes é o padrão da indústria para orquestração de containers em escala:

Vantagens:

  • Portabilidade entre clouds (AWS EKS, GCP GKE, Azure AKS, on-premise)
  • Ecossistema enorme (Helm, Istio, ArgoCD, Prometheus, Grafana)
  • Auto-scaling sofisticado (HPA, VPA, Cluster Autoscaler)
  • Service mesh para observabilidade e segurança entre serviços

Limitações:

  • Complexidade operacional alta (precisa de SRE dedicado)
  • Curva de aprendizado íngreme
  • Custo do control plane (EKS cobra por cluster)
  • Overhead para aplicações simples

Quando usar: equipes com 10+ devs, mais de 10 serviços, requisitos de multi-cloud ou on-premise, quando já tem experiência com K8s.

Decisão prática que aplicamos na Bradata

Para a maioria dos projetos enterprise que desenvolvemos:

  • MVP e primeiros 2 anos: ECS Fargate na AWS ou Cloud Run no GCP: simplicidade e custo baixo
  • Escala média (10+ serviços): EKS (Kubernetes gerenciado na AWS) com Helm charts
  • Escala enterprise ou multi-cloud: Kubernetes com GitOps (ArgoCD)

A migração de ECS para Kubernetes é relativamente simples porque o artefato é o mesmo (imagem Docker). O que muda é a configuração de deploy (Task Definition do ECS → Kubernetes manifests).

Estratégias de deploy: zero downtime em produção

Em sistemas enterprise, deploy com downtime é inaceitável. Três estratégias de zero-downtime deploy:

Rolling update

O padrão mais simples: novas instâncias são criadas gradualmente enquanto as antigas são removidas.

# Kubernetes Deployment com rolling update
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    spec:
      containers:
        - name: api
          image: ghcr.io/empresa/api:v2.1.0
          readinessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 30
            periodSeconds: 10

Como funciona: com 4 réplicas e maxUnavailable: 1, o Kubernetes atualiza 1 pod por vez. Enquanto o pod novo não passa no readiness probe, o pod antigo continua recebendo tráfego.

Blue-green deploy

Duas versões do ambiente rodam simultaneamente. O tráfego é migrado de uma vez do "blue" (versão atual) para "green" (versão nova).

Antes do deploy:
  Load Balancer → Blue (v1.0) [100% do tráfego]
                  Green (v2.0) [0% - em teste]

Cutover:
  Load Balancer → Blue (v1.0) [0% - standby]
                  Green (v2.0) [100% do tráfego]

Rollback (se necessário):
  Load Balancer → Blue (v1.0) [100% - restaurado]
                  Green (v2.0) [0% - desligado]

Vantagens: rollback instantâneo (basta mudar o apontamento do load balancer), versão nova é testada em ambiente real antes de receber tráfego.

Custo: dobro de infraestrutura durante o deploy (dois ambientes completos rodando).

Canary deploy

Uma pequena porcentagem do tráfego (5%) é direcionada para a versão nova. Se as métricas estiverem saudáveis por 10 minutos, o tráfego é aumentado gradualmente (25%, 50%, 100%). Ideal para sistemas com alto tráfego onde um bug pode impactar milhares de usuários.

Qual estratégia usar

CenárioEstratégia recomendada
Aplicação web padrão, equipe pequenaRolling update
Sistema crítico, precisa de rollback instantâneoBlue-green
Alto tráfego, releases frequentesCanary
MVP / early-stageRolling update

Infraestrutura como código: Terraform

Infraestrutura definida em código (IaC) garante reprodutibilidade e versionamento do ambiente:

# Exemplo: ECS Fargate com Terraform
resource "aws_ecs_service" "api" {
  name            = "api-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.api.arn
  desired_count   = 2
  launch_type     = "FARGATE"

  network_configuration {
    subnets         = var.private_subnets
    security_groups = [aws_security_group.api.id]
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.api.arn
    container_name   = "api"
    container_port   = 3000
  }

  deployment_configuration {
    maximum_percent         = 200
    minimum_healthy_percent = 100
  }
}

resource "aws_ecs_task_definition" "api" {
  family                   = "api"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 512
  memory                   = 1024

  container_definitions = jsonencode([{
    name  = "api"
    image = "${var.ecr_repository}:${var.image_tag}"
    portMappings = [{
      containerPort = 3000
      protocol      = "tcp"
    }]
    healthCheck = {
      command     = ["CMD-SHELL", "wget -qO- http://localhost:3000/api/health || exit 1"]
      interval    = 30
      timeout     = 5
      retries     = 3
      startPeriod = 60
    }
    logConfiguration = {
      logDriver = "awslogs"
      options = {
        "awslogs-group"         = "/ecs/api"
        "awslogs-region"        = var.aws_region
        "awslogs-stream-prefix" = "ecs"
      }
    }
  }])
}

Princípio fundamental: tudo que está em produção precisa estar no código. Se um recurso foi criado manualmente no console da AWS, ele não existe oficialmente: pode ser deletado, esquecido ou inconsistente.

Monitoramento: os 4 sinais dourados

Uma aplicação em produção sem monitoramento é um avião sem instrumentos. Os 4 sinais dourados (Google SRE):

1. Latência

Tempo que cada request leva para ser processada.

  • p50: metade das requests são mais rápidas que esse valor (experiência típica)
  • p95: 95% das requests são mais rápidas (experiência de "quase todo mundo")
  • p99: 1% das requests mais lentas (detecta problemas pontuais)

Alerta: p95 acima de 500ms por mais de 5 minutos.

2. Tráfego

Volume de requests por segundo (RPS). Monitore por endpoint e por status code.

Alerta: queda abrupta de tráfego (pode indicar que o sistema está inacessível) ou aumento inesperado (possível ataque ou viral).

3. Erros

Taxa de respostas com status 5xx (erro do servidor). Separar de 4xx (erro do cliente, geralmente esperado).

Alerta: taxa de 5xx acima de 1% por mais de 2 minutos.

4. Saturação

Uso de CPU, memória, disco e conexões de banco. Quanto mais próximo do limite, mais instável o sistema.

Alerta: CPU acima de 80% por mais de 10 minutos, memória acima de 85%, conexões de banco acima de 75% do pool.

Stack de monitoramento recomendada

FerramentaFunçãoAlternativa
GrafanaDashboards e visualizaçãoDatadog
PrometheusColeta de métricasCloudWatch Metrics
LokiAgregação de logsElasticsearch, CloudWatch Logs
JaegerTracing distribuídoGrafana Tempo, AWS X-Ray
SentryTracking de errosBugsnag, Rollbar
PagerDutyAlertas e escalaçãoOpsGenie, Grafana OnCall

Gestão de secrets

Secrets (senhas de banco, API keys, certificados) nunca devem estar no código. A hierarquia correta: defaults no código para valores não-sensíveis, .env para desenvolvimento local (nunca commitado), GitHub Secrets para pipelines de CI/CD, e AWS Secrets Manager ou HashiCorp Vault para produção. Rotação automática é obrigatória: banco de dados a cada 30 dias, API keys a cada 90 dias, certificados TLS com renovação automática via cert-manager ou ACM.

Disaster recovery e migrações seguras

Backup e restore

  • Banco de dados: snapshots automáticos diários + point-in-time recovery
  • Storage (S3): versionamento habilitado + replicação cross-region para dados críticos
  • Configuração: toda em código (Terraform), pode ser recriada do zero
  • Teste de restore: executar restore mensal em ambiente isolado para validar integridade

Migrações de banco em produção

Migrações de banco de dados são o deploy mais arriscado. Regras fundamentais:

  1. Migrações são forward-only: nunca faça DROP COLUMN na mesma release que remove o código que usa a coluna
  2. Expand-contract pattern: adiciona coluna nova, migra dados, remove coluna antiga (em releases separadas)
  3. Sem locks: use CREATE INDEX CONCURRENTLY para evitar bloqueios em tabelas grandes
  4. Teste com volume real: execute a migração em staging contra dados de produção antes do deploy

Checklist DevOps para aplicações enterprise

Antes de considerar que a infraestrutura está pronta para produção:

CI/CD:

  • Pipeline automatizado com lint, testes e build a cada push
  • Security scan de dependências e código
  • Deploy automatizado para staging
  • Deploy para produção com aprovação ou gate automático
  • Rollback documentado e testado

Containers:

  • Dockerfile multi-stage com imagem mínima
  • Health check configurado no container
  • Usuário non-root no container
  • Nenhum secret hardcoded na imagem

Infraestrutura:

  • Toda infra definida como código (Terraform/Pulumi)
  • Secrets em secret manager (não em variáveis de ambiente do CI)
  • Backup automático com teste de restore
  • SSL/TLS em todos os endpoints

Monitoramento:

  • Dashboards com os 4 sinais dourados
  • Alertas configurados com escalação
  • Logs centralizados e pesquisáveis
  • Tracing distribuído (se mais de 3 serviços)

Conclusão: DevOps é investimento, não custo

Implementar práticas de DevOps robustas exige investimento inicial: pipeline de CI/CD, containerização, infraestrutura como código, monitoramento. Mas o retorno é composto: cada deploy fica mais rápido, mais seguro e mais rastreável.

Na Bradata, investimos as primeiras 2 semanas de cada projeto configurando o pipeline completo. Quando o projeto tem 50 deploys por mês durante 2 anos, o tempo economizado em deploys manuais e rollbacks emergenciais paga o investimento várias vezes. DevOps não é sobre ferramentas: é sobre tornar o deploy uma rotina invisível, não um evento estressante.

Precisa de um talento tech agora?

Fale com a Bradata e receba uma proposta em 24 horas úteis.