Executando verificação de segurança...
1

DevOps para Devs: Docker, CI/CD, Kubernetes e AWS em Produção

O problema real

Você escreve o código, abre o PR, merge na main e... algo acontece. Minutos depois, a feature está em produção. Ou não está, porque o pipeline quebrou e ninguém do time de desenvolvimento sabe ler o log do Kubernetes.

Esse post não é sobre transformar você em SRE. É sobre cobrir o mínimo de infraestrutura que permite a um dev backend diagnosticar problemas, propor melhorias no pipeline e conversar de igual para igual com o time de plataforma. Vou cobrir Docker, CI/CD com GitHub Actions, Kubernetes e os serviços AWS mais comuns em stacks de produção.

Se você já trabalha com Node.js e TypeScript no backend, como discuti no guia de engenharia backend, este post é a continuação natural: o que acontece com seu código depois que ele sai do editor.

Docker: do Dockerfile ao container em produção

A maioria dos devs sabe rodar docker build e docker run. O problema aparece quando a imagem pesa 1.2 GB, demora 8 minutos para buildar e o container roda como root. Vamos resolver os três.

Multi-stage build para Node.js

A ideia do multi-stage é separar o ambiente de build (onde você precisa de devDependencies, compilador TypeScript, etc.) do ambiente de runtime (onde só precisa do JavaScript compilado e das dependências de produção).

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

# Estágio 2: runtime
FROM node:20-alpine AS runner
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]

Três coisas importam aqui. Primeiro, node:20-alpine em vez de node:20: a imagem base cai de ~900 MB para ~130 MB. Segundo, npm ci --omit=dev no estágio de runtime garante que ferramentas como typescript, jest e eslint não vão para produção. Terceiro, USER appuser evita rodar o processo como root, o que é um vetor de ataque trivial em caso de RCE.

.dockerignore que funciona

Sem um .dockerignore adequado, o COPY envia node_modules, .git e artefatos de teste para o daemon. Isso torna o build lento e a imagem desnecessariamente grande.

node_modules
.git
.env*
dist
coverage
*.md
docker-compose*.yml
.github

Testando localmente antes de subir

# Build com tag versionada
docker build -t minha-api:1.0.0 .

# Rodar com variáveis de ambiente
docker run -d \
  --name api-local \
  -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:pass@host.docker.internal:5432/mydb" \
  minha-api:1.0.0

# Verificar logs
docker logs -f api-local

# Inspecionar tamanho final
docker images minha-api:1.0.0 --format "{{.Size}}"

O host.docker.internal resolve para o host da máquina no Docker Desktop (macOS e Windows). Em Linux, você precisa adicionar --add-host=host.docker.internal:host-gateway ou usar a rede host.

CI/CD com GitHub Actions

Se você já leu o post sobre CI/CD para Next.js na Vercel, viu o fluxo para apps frontend. Para backend em container, o pipeline muda: precisamos buildar a imagem Docker, rodar testes dentro dela (ou antes) e fazer push para um registry.

Pipeline completo para API Node.js

name: CI/CD Backend

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

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

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

---

Leia o artigo completo em [https://vivodecodigo.com.br/infra/devops-para-devs-docker-cicd-kubernetes-aws-producao](https://vivodecodigo.com.br/infra/devops-para-devs-docker-cicd-kubernetes-aws-producao)
Carregando publicação patrocinada...