Do código no notebook até a aplicação rodando na AWS
Engenheiro DevOps com experiência em ambientes de produção de alta demanda. Atuou em grandes corporações do setor financeiro. O conteúdo não é teórico — é o que funciona lá fora.
Certified Kubernetes Administrator · CNCF
Solutions Architect Associate · Amazon
Observability & Monitoring · Certified
PHP Conference Brasil
"Tudo que você vai ver nesses dois dias eu já coloquei em produção — com pessoas dependendo disso funcionando. Não tem teoria aqui."
Bases para você nunca mais dizer "funciona na minha máquina"
Em duplas ou individualmente: desenhe o caminho de uma aplicação do seu notebook até chegar ao usuário final.
Identifique cada etapa, a ferramenta que entra em ação e onde podem ocorrer falhas. Depois apresente para a turma.
Onde o código é escrito e testado localmente
Build, imagem, pipeline e registro
Servidor, DNS, load balancer, monitoramento
| Comando | Pra que serve |
|---|---|
pwd / ls / cd |
Navegar no sistema |
cat / tail -f |
Ler arquivos e logs em tempo real |
grep "erro" app.log |
Filtrar linhas de log |
ps aux |
Ver processos em execução |
chmod 600 chave.pem |
Ajustar permissões |
env | grep DATABASE |
Inspecionar variáveis de ambiente |
ssh -i key.pem user@ip |
Acessar servidor remoto |
scp arquivo user@ip:/dest |
Copiar arquivo para servidor |
Subir aplicação local, acessar via porta e investigar
.gitignore — o que NUNCA commitar.env, credenciais ou chaves SSH. Uma
vez no histórico, está comprometido para sempre.Clonar o repositório do curso, criar branch com seu nome, alterar o arquivo README.md e
abrir Pull Request.
.env
.env.prod
node_modules/
dist/
*.pem
*.key
O molde. Criada com docker build. Imutável, versionada.
Instância em execução da imagem. Pode ter várias do mesmo molde.
Receita para construir a imagem: base OS, deps, código, cmd.
Onde as imagens ficam armazenadas. Docker Hub, ECR, GHCR.
# ── Etapa 1: instalar só dependências de produção ──
FROM node:22-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ── Etapa 2: imagem final enxuta ──────────────────
FROM node:22-alpine
WORKDIR /app
# Copia apenas o resultado da etapa anterior
COPY --from=deps /app/node_modules ./node_modules
COPY src ./src
COPY package.json ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "src/app.js"]
| Base | Tamanho | |
|---|---|---|
node:22 |
1.1 GB | ✕ |
node:22-slim |
230 MB | ~ |
node:22-alpine |
67 MB | ✓ |
| Multi-stage build | ~45 MB | ✓✓ |
.dockerignore — node_modules/ e
tests/ fora da imagem de produção.
Fazer o build da imagem da tasks-api, rodar o container e validar o endpoint
/health via curl.
depends_on — ordem de inicializaçãoservices:
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: tasksdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5
api:
build:
context: ./backend
dockerfile: Dockerfile.dev
environment:
DB_HOST: db # nome do serviço = hostname na rede
DB_NAME: tasksdb
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
frontend:
build: ./frontend
ports:
- "8080:80"
depends_on:
- api
volumes:
pg_data:
docker attach + CTRL+C é interpretado pelo Docker como parada manual — o restart não dispara. Para simular um crash de verdade use docker exec <container> kill -9 1. api:
image: tasks-api:latest
restart: unless-stopped # recomendado
db:
image: postgres:16-alpine
restart: unless-stopped
| Política | Crash | Stop manual | Reboot |
|---|---|---|---|
no | ✕ | ✕ | ✕ |
on-failure | ✓ | ✕ | ✕ |
unless-stopped | ✓ | ✕ | ✓ |
always | ✓ | ~* | ✓ |
always: ignora stop manual no reboot — container volta mesmo que você tenha parado intencionalmente.unless-stopped — reinicia em falha e reboot, mas respeita paradas intencionais de deploy e manutenção.5432.
actions/checkout@v4, actions/setup-node@v4, gitleaks/gitleaks-action@v2..github/workflows/ci.yml — define tudoon:needs:run: (shell) ou uses: (action do marketplace)push — não em PRsjobs:
test: # tier 1 — paralelo
runs-on: ubuntu-latest
# ...
security: # tier 1 — paralelo
runs-on: ubuntu-latest
# ...
build: # tier 2 — espera os dois
needs: [test, security]
runs-on: ubuntu-latest
# ...
push: # tier 3
needs: build
if: github.event_name == 'push'
# ...
deploy: # tier 4
needs: push
if: github.event_name == 'push'
environment: production # aparece no GitHub
# ...
healthcheck: # tier 5
needs: deploy
if: github.event_name == 'push'
# ...
security:
name: 🔒 Segurança
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # histórico completo
- name: Gitleaks — verificar secrets
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build:
needs: [test, security]
steps:
- name: Build imagem da API
run: docker build -t tasks-api:${{ github.sha }} ./backend
- name: Exportar imagem
run: |
docker save tasks-api:${{ github.sha }} \
| gzip > tasks-api.tar.gz
- uses: actions/upload-artifact@v4 # salva no GitHub
with:
name: docker-images
path: tasks-api.tar.gz
retention-days: 1
actions/download-artifact para carregar essa imagem e publicá-la no ECR — sem reconstruir.Com o .github/workflows/ci.yml já criado:
demopassword=abc123) e ver o Gitleaks bloquearAplicação containerizada, rodando localmente com Docker Compose, e pipeline CI/CD completa no GitHub Actions — do commit ao health check automático.
Do container local ao servidor público na AWS com pipeline automatizado
Servidor virtual. Você controla tudo: OS, Docker, portas.
Firewall da instância. Regras de entrada e saída por porta/IP.
Par de chaves RSA para acesso SSH sem senha.
Identidades e permissões. Princípio do menor privilégio.
Registry de imagens Docker privado da AWS.
DNS gerenciado. Conecta domínio ao IP da EC2.
AmazonEC2ContainerRegistryReadOnly — sem
credenciais hardcoded.0.0.0.0/0 na porta 22 em produção.
Criar uma instância EC2 (Ubuntu 24.04, t3.micro), acessar via SSH e instalar Docker.
docker pull ou build direto na EC2docker run -e ou --env-filecurl http://IP_EC2/health| Secret | Valor |
|---|---|
AWS_ACCESS_KEY_ID |
Credencial IAM para push no ECR |
AWS_SECRET_ACCESS_KEY |
Credencial IAM para push no ECR |
EC2_HOST |
IP público da instância |
EC2_SSH_KEY |
Conteúdo da chave PEM privada |
APP_ENV |
Conteúdo do .env de produção |
deploy:
needs: build # só roda se CI passou
runs-on: ubuntu-latest
steps:
- name: Configurar credenciais AWS
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: sa-east-1
- name: Deploy via SSH na EC2
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.EC2_HOST }}
username: ubuntu
key: ${{ secrets.EC2_SSH_KEY }}
script: |
aws ecr get-login-password --region sa-east-1 \
| docker login --username AWS --password-stdin $ECR_URL
docker pull $ECR_URL/app:latest
docker stop app || true
docker rm app || true
docker run -d --name app \
--env-file /etc/app/.env \
-p 80:3000 \
--restart unless-stopped \
$ECR_URL/app:latest
docker image prune -f
Alterar qualquer texto na aplicação, fazer commit e push na main. Observar a pipeline no
GitHub Actions executar o deploy completo.
docker pull app:sha-anterior e reiniciar.| Dev | Staging | Prod | |
|---|---|---|---|
DATABASE_URL |
localhost:5433 | rds-stg:5432 | rds-prod:5432 |
MP_ACCESS_TOKEN |
TEST-xxx | TEST-xxx | APP_USR-xxx |
MP_SANDBOX |
true | true | false |
LOG_LEVEL |
debug | info | warn |
const db = new DB({
host: 'rds-prod.abc.rds.amazonaws.com',
password: 'p@ssword123' // hardcoded!
});
const db = new DB({
host: process.env.DB_HOST,
password: process.env.DB_PASS,
});
Mudar uma variável de ambiente da aplicação em produção sem alterar uma linha de código.
No .gitignore. Nunca commitar. Copiar de .env.example.
Credenciais usadas na pipeline. Visível apenas para admins do repo.
Arquivo na instância, criado pelo primeiro deploy, atualizado via pipeline.
O que aconteceu, quando e em qual contexto. Primeiro lugar que você olha.
docker logs app -f
docker logs app \
--since 10m \
--tail 100
Números ao longo do tempo. Latência, erros/min, CPU, memória, uptime.
docker stats app
# resposta do endpoint:
GET /health
→ 200 OK 12ms ✓
→ 500 ERR -- ✗
Caminho de uma requisição pelo sistema — identifica onde ela ficou lenta ou quebrou.
Para esta turma: foco em logs + métricas. Tracing é próximo nível.
// NestJS — health controller
@Controller('health')
export class HealthController {
@Get()
check(): object {
return {
status: 'ok',
uptime: process.uptime(),
memory: process.memoryUsage().rss,
db: 'connected',
version: process.env.npm_package_version,
};
}
}
// Resposta esperada em produção
// GET /health → 200 OK
// {
// "status": "ok",
// "uptime": 7234,
// "db": "connected"
// }
| Status | Significa |
|---|---|
| 200 OK | Tudo funcionando |
| 301/302 | Redirect — pode ser intencional |
| 401/403 | Problema de autenticação/permissão |
| 404 | Rota não encontrada — bug ou typo |
| 500 | Erro no servidor — olhe os logs |
| 502/503 | Container caiu ou Nginx sem backend |
Simular erro na aplicação, identificar nos logs e no health check, restaurar.
docker ps — verificar se o container existe e o statusdocker logs app --tail 50 — ler as últimas linhasss -tlnp | grep 3000 — confirmar bindingdocker exec app env | grep DATABASE-pAplicação publicada na AWS com pipeline automatizado rodando — e aluno capaz de investigar e corrigir falhas básicas em produção.
Ambiente quebrado entregue pelos instrutores. Os alunos precisam diagnosticar e restaurar o serviço. Sem dicas sobre qual é o problema.
Rodando na AWS com Docker, HTTPS e CI/CD configurados do zero por você.
Push → deploy. Sem intervenção manual, sem medo de sobrescrever algo em prod.
Saber investigar logs, diagnosticar erros e restaurar um serviço sob pressão.