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)