Portfólio técnico: montar um case deployável em 1 hora com GitHub Actions e Terraform
Por John Pires · 14 de março de 2026

Portfólio técnico: case deployável em 1 hora com GitHub Actions e Terraform
1) Problema em escala
Contexto do problema
Com 1–3 anos de experiência, você precisa de cases que provem capacidade técnica, pensamento arquitetural e cuidado com entrega contínua. Entregar um portfólio que é apenas um repositório com HTML estático não basta: entrevistadores esperam ver pipelines, infraestrutura como código, controles de segurança e automação reproduzível. O desafio é montar um case que seja deployável em menos de 1 hora, repetível e que demonstre práticas reais de DevOps.
Impacto em entrevistas e avaliações técnicas
Um case deployável rapidamente facilita demonstrações ao vivo durante entrevistas e permite que você compartilhe um link acessível. Além disso, um projeto com GitHub Actions + Terraform mostra proficiência em CI/CD e IaC — dois tópicos que aparecem em entrevistas de nível intermediário. Recrutadores e engenheiros seniores valorizam ver: pipelines legíveis, segredos bem tratados, roles e políticas mínimas e observabilidade básica.
Regra prática: um case técnico deve ser pequeno o bastante para ser reproduzido em 1 hora, completo o bastante para revelar suas escolhas arquiteturais.
2) Comparativo técnico de abordagens
Opções rápidas e suas trocas
Existem várias maneiras de publicar um portfólio: GitHub Pages, Netlify/Vercel, S3 + CloudFront, ou um container em ECS/Fargate. Para um case que mostre IaC e pipeline, comparar duas abordagens é útil:
- Netlify/Vercel: deploy instantâneo, pouco IaC, excelente para front-end, mas não mostra habilidades em gestão de infra cloud.
- S3 + CloudFront (AWS): requer Terraform para provisioning, configurações de segurança e pipeline de deploy; ótimo para demonstrar controle de infraestrutura.
Por que escolher Terraform + GitHub Actions?
GitHub Actions provê integração nativa com o repositório e controle de secrets. Terraform dá rastreabilidade (estado) e versionamento da infraestrutura. Essa combinação torna seu case auditável, reproduzível e alinhado com práticas de produção. Para entrevistas, ela permite que você discuta trade-offs de segurança (OAI vs. site público), política de IAM e estratégias de rollback.
3) Implementação production-ready
Estrutura mínima do repositório (o que incluir)
Seu repositório deve ter uma estrutura clara que qualquer avaliador possa clonar e executar. Exemplo prático de estrutura mínima e propósito de cada pasta/arquivo segue abaixo. Isso comunica organização e maturidade profissional.
# Estrutura proposta do repositório
README.md # instruções de uso e link do case
infra/
├── main.tf # definição da infra (S3, CloudFront, IAM)
├── variables.tf
├── outputs.tf
└── versions.tf
site/
├── index.html # landing do portfolio com links para cases
└── assets/
.github/workflows/
└── ci-cd.yml # pipeline que aplica terraform e publica o site
O que acontece: ao clonar, o avaliador encontra README com passos claros; rodando o workflow, a infra é provisionada e o conteúdo do diretório site/ é sincronizado para o bucket público ou distribuição CloudFront.
Exemplo de página do portfólio (index.html)
Antes de provisionar, tenha um conteúdo real no site. Inclua um case específico que demonstre decisões técnicas (ex.: link para um microserviço que você construiu, arquitetura em markdown, e logs de performance).
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Portfólio Técnico - Case Deployável</title>
</head>
<body>
<h1>Portfólio Técnico</h1>
<p>Case: Infra como Código + CI/CD. Link para o repositório: <a href="https://github.com/seu-usuario/seu-repo">repo</a></p>
<section>
<h2>Highlights técnicos</h2>
<ul>
<li>Terraform para provisionamento de S3 + CloudFront</li>
<li>GitHub Actions para pipeline de IaC e deploy</li>
<li>Testes de carga automatizados com k6</li>
</ul>
</section>
</body>
</html>
O que acontece após o deploy: esse HTML será servido pelo CloudFront/S3. Inclua no README instruções para reproduzir o deploy localmente.
Terraform: main.tf mínimo e seguro
Contexto: este trecho cria um bucket S3 (bloqueia ACLs públicos por padrão), cria um usuário AWS com permissão restrita para deploy e uma CloudFront distribution básica. Ajuste nomes e regiões.
provider "aws" {
region = var.region
}
resource "aws_s3_bucket" "site_bucket" {
bucket = var.bucket_name
acl = "private"
force_destroy = true
versioning {
enabled = true
}
}
resource "aws_cloudfront_origin_access_identity" "oai" {
comment = "OAI for portfolio site"
}
resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = aws_s3_bucket.site_bucket.id
policy = data.aws_iam_policy_document.bucket_policy.json
}
data "aws_iam_policy_document" "bucket_policy" {
statement {
actions = ["s3:GetObject"]
principals {
type = "AWS"
identifiers = [aws_cloudfront_origin_access_identity.oai.iam_arn]
}
resources = ["${aws_s3_bucket.site_bucket.arn}/*"]
}
}
resource "aws_cloudfront_distribution" "cdn" {
origin {
domain_name = aws_s3_bucket.site_bucket.bucket_regional_domain_name
origin_id = "S3-${aws_s3_bucket.site_bucket.id}"
s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.oai.cloudfront_access_identity_path
}
}
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3-${aws_s3_bucket.site_bucket.id}"
forwarded_values {
query_string = false
cookies { forward = "none" }
}
viewer_protocol_policy = "redirect-to-https"
}
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html"
restrictions { geo_restriction { restriction_type = "none" } }
viewer_certificate { cloudfront_default_certificate = true }
}
output "cloudfront_domain" {
value = aws_cloudfront_distribution.cdn.domain_name
}
O que acontece: ao aplicar este Terraform, você terá um bucket privado, uma distribuição CloudFront que acessa o bucket via OAI e um output com o domínio do CDN. Isso evita expor diretamente o bucket.
GitHub Actions: pipeline reproducível
Contexto: o workflow abaixo executa validação do Terraform, aplica a infra se necessário e depois publica o conteúdo do diretório site/ para o bucket S3. Configure secrets AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY no repo.
name: ci-cd
on:
push:
branches: [ main ]
jobs:
infra:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.5.0
- name: Terraform Init
run: terraform -chdir=infra init -input=false
- name: Terraform Format
run: terraform -chdir=infra fmt -check
- name: Terraform Validate
run: terraform -chdir=infra validate
- name: Terraform Plan
id: plan
run: terraform -chdir=infra plan -out=tfplan
- name: Terraform Apply
if: github.ref == 'refs/heads/main'
run: terraform -chdir=infra apply -input=false -auto-approve tfplan
deploy_site:
needs: infra
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Sync site to S3
run: |
aws s3 sync site/ s3://${{ env.BUCKET_NAME }} --delete --acl private
aws cloudfront create-invalidation --distribution-id ${{ steps.infra.outputs.cloudfront_id }} --paths "/*"
env:
BUCKET_NAME: your-bucket-name
O que acontece: o job infra cria/atualiza a infraestrutura; o deploy_site sincroniza o conteúdo e invalida o cache da CloudFront. Em entrevistas, explique como você minimiza blast radius limitando permissões do usuário usado pelo Actions.
4) Testes de carga e resiliência
Por que testar o case?
Mesmo um site estático pode sofrer tráfego inesperado em demonstrações públicas. Exibir que você incluiu testes de carga simples mostra preocupação com escalabilidade. Além disso, implementar testes automatizados (k6) no pipeline demonstra habilidade para pensar em SLOs e limites operacionais.
Exemplo de teste com k6
Contexto: script k6 que bate no root do site e coleta latências. Roda localmente ou como job no GitHub Actions. Modifique BASE_URL para o domínio CloudFront de outputs do Terraform.
import http from 'k6/http';
import { check, sleep } from 'k6';
export let options = {
vus: 50,
duration: '30s',
};
export default function () {
const res = http.get(__ENV.BASE_URL || 'https://example.cloudfront.net/');
check(res, { 'status was 200': (r) => r.status === 200 });
sleep(1);
}
O que acontece: esse teste simula 50 usuários por 30s acessando a raiz. Você obterá métricas de latência e taxa de sucesso. Inclua análise simples no README com limites aceitáveis para a demo.
Testes de resiliência: invalidação e rollback
Inclua passos no pipeline para criar backups das versões publicadas (ex.: prefixo com timestamp no S3) antes do sync e um script de rollback que restaura a versão anterior. Isso evidencia maturidade operacional em seu case técnico.
5) Evolução e manutenção a longo prazo
Práticas para manter o case saudável
Para que o case continue sendo útil ao longo do tempo, adote algumas práticas: automatize atualização de dependências, mantenha terraform lock e versões declaradas em versions.tf, crie testes de integração simples e documente decisões arquitônicas no README. Essas ações reduzem o esforço quando for mostrado em entrevistas meses depois.
Monitoramento, observabilidade e custos
Adicione métricas básicas: request logs no CloudFront (ou S3 access logs), configurar alertas de custo no AWS e dashboard minimal em Grafana/CloudWatch para latência. Em uma entrevista, esses detalhes demonstram que você considera trade-offs entre visibilidade e custo.
Exemplo de comando de rollback (script)
Contexto: script bash que restaura a versão anterior do site a partir de um prefixo histórico no S3. Útil para demos onde um deploy quebrou a página do portfólio.
#!/usr/bin/env bash
# rollback.sh: restaura a versão mais recente com prefixo backup/
set -e
BUCKET="your-bucket-name"
LATEST=$(aws s3 ls s3://$BUCKET/backup/ --recursive | sort | tail -n 1 | awk '{print $4}')
if [ -z "$LATEST" ]; then
echo "Nenhum backup encontrado"
exit 1
fi
aws s3 sync s3://$BUCKET/backup/$LATEST/ s3://$BUCKET/ --delete
aws cloudfront create-invalidation --distribution-id YOUR_DISTRIBUTION_ID --paths "/*"
echo "Rollback aplicado: $LATEST"
O que acontece: o script copia o conteúdo do prefixo backup mais recente de volta para o bucket principal e invalida o cache. Explique durante entrevistas como esse padrão reduz tempo de recuperação (MTTR).
Erros comuns, armadilhas e dicas práticas
Erros frequentes
Listo erros que vi em casos de portfólio e em entrevistas técnicas:
- Deixar credenciais permanentes no código ou em arquivos versionados.
- Publicar bucket S3 sem controle (bloquear ACLs e usar OAI para CloudFront).
- Não definir versões de Terraform/Providers — quebra builds no CI.
- Pipeline que faz apply automático sem revisão em branches protegidas.
Dicas práticas (checklist)
Antes de apresentar seu case em uma entrevista, confirme:
- README com passos claros para rodar localmente e reproduzir o deploy.
- Rotina para rotacionar as credenciais usadas pelo GitHub Actions.
- Checks básicos no CI: terraform fmt, terraform validate, lint de HTML/CSS e smoke tests depois do deploy.
Dica: crie um branch demo com um workflow que só aplica infra quando um label ou PR específico for aprovado — isso reduz deploys acidentais no seu main.
Conclusão prática
Resumo dos passos para entregar em 1 hora
1) Tenha um site minimal (site/index.html). 2) Crie main.tf com bucket S3 + CloudFront e outputs claros. 3) Configure GitHub Actions com steps para terraform init/plan/apply e sync. 4) Configure secrets do repo. 5) Rode o workflow e valide o domínio do CloudFront. Em condições ideais, isso é reproduzível em 1 hora.
O que apresentar na entrevista
Na hora de mostrar o case, foque em decisões: por que IaC, como gerencia segredos, como o pipeline minimiza riscos, que trade-offs de segurança escolheu (OAI vs. site público), e como efetuaria rollback. Mostre o código do Terraform, o workflow e um resumo das métricas de carga se tiver rodado o k6.
Próximos passos para evolução
Depois do case inicial, evolua para: HTTPS customizado com ACM, integração com CI para testes end-to-end, e um pequeno backend serverless (Lambda + API Gateway) para demonstrar integração entre front e APIs. Cada evolução vira um ponto de conversa em entrevistas e aumenta o valor do seu portfólio.
Este guia priorizou reproduzibilidade e práticas de produção. Um case deployável em 1 hora não é um demo improvisado: é um produto mínimo viável que demonstra suas decisões técnicas, a disciplina com pipelines e o cuidado com segurança e observabilidade.
Ao terminar, atualize o README com links diretos para os outputs (domínio do CloudFront), instruções de rollback e um changelog simples. Isso transforma um repositório em uma peça profissional do seu portfólio.
Assuntos: DevOps, github-actions, terraform, AWS, portfolio
Leia também
Testcontainers e GitHub Actions: testar imagens Docker na CI corretamente
Guia prático de como rodar testes de integração com imagens Docker usando Testcontainers no GitHub Actions: padrões certos e anti-patterns, workflows…
Testes em CI com GitHub Actions e Docker Compose para Postgres e Redis
Aprenda a rodar testes de integração no CI usando GitHub Actions e Docker Compose com Postgres e Redis. Guia passo a passo para iniciantes: arquivos…
Validando assinaturas de imagens Docker no Kubernetes com cosign e OPA Gatekeeper
Guia prático para validar assinaturas de imagens Docker no Kubernetes usando cosign e OPA Gatekeeper: padrões, exemplos com CI (GitHub Actions)…
GitHub Actions + OIDC: provisionar infraestrutura com Terraform sem credenciais
Aprenda a usar OIDC do GitHub Actions para provisionar infraestrutura com Terraform na AWS sem armazenar credenciais. Guia prático com Terraform, IAM…
GitHub Actions OIDC: deploy sem segredos em AWS, GCP e Azure (setup prático)
Guia prático para configurar GitHub Actions OIDC e eliminar secrets nas nuvens: passo a passo para AWS, GCP e Azure, exemplos Terraform/gcloud/workflows e…
Escanear imagens Docker no CI com Trivy e GitHub Actions — falhe por CVSS
Aprenda a escanear imagens Docker no CI com Trivy e GitHub Actions e a fazer o pipeline falhar quando uma vulnerabilidade atingir um limiar CVSS. Passo a…