DEV Community

Cover image for Hospedando sites de graça na AWS + Cloudflare
Mark Gerald Martins
Mark Gerald Martins

Posted on

Hospedando sites de graça na AWS + Cloudflare

Salve, salve! Bora subir um site na AWS pagando praticamente nada e ainda com pipeline de deploy automático no GitHub? É isso que esse post vai te mostrar, sem enrolação.

A ideia aqui é simples: pegar um repositório pronto (open source, MIT, link no final), entender o que tá rolando por baixo dos panos e adaptar pro seu projeto. Pode ser um portfólio, landing page, doc estática, SPA em React, Vue, Vite, Astro. Se gera HTML/CSS/JS no final, serve.

Antes de cair no código, vamos alinhar três conceitos rapidinho. Quem já manja, pula pra parte de mão na massa.

AWS, o que é isso aí?

AWS é o serviço de nuvem da Amazon. Em vez de você comprar um servidor, plugar na tomada e rezar pra não cair, você aluga "pedaços" de infraestrutura por uso. Vai do simples (guardar um arquivo no S3) até o complexo (cluster Kubernetes gerenciado, banco multi-região, IA generativa, etc).

Pra hospedar site estático, a gente vai usar quatro coisinhas básicas:

  • S3: storage de objetos. Pensa numa pasta na nuvem onde você joga seus arquivos.
  • CloudFront: CDN global. Distribui o site nos servidores de borda da AWS pelo mundo, então o cara em Tóquio carrega rápido igual ao cara em São Paulo.
  • ACM: gerenciador de certificados SSL. HTTPS de graça, renovação automática.
  • IAM: controle de quem pode fazer o quê. Permissões, roles, etc.

E a Cloudflare, fora da AWS, entra como provedora de DNS. Plano free dela já resolve, sem precisar comprar Route 53.

Infra como código (IaC), pra que serve?

Resumo da ópera: em vez de clicar no console da AWS, você descreve a infraestrutura em arquivos de código. Vantagens:

  • Versiona no Git.
  • Sobe em qualquer ambiente com um comando.
  • Se quebrou, dá pra rever no diff.
  • Code review na infra, mano.

As ferramentas mais conhecidas: Terraform, Pulumi, CloudFormation (da AWS) e AWS CDK, que é o que a gente vai usar.

AWS CDK, qual a sacada?

CDK = Cloud Development Kit. É a forma "moderna" de escrever infra na AWS. Em vez de escrever YAML/JSON gigante (CloudFormation puro), você escreve TypeScript, Python, Java, Go ou C#. O CDK compila aquilo em CloudFormation e a AWS aplica.

Vantagem: você ganha autocomplete, tipagem, abstrações de alto nível (chamadas de "constructs"). Em vez de configurar 20 recursos pra um CloudFront com OAC, S3 privado e bucket policy, você instancia uma classe e pronto.

Exemplo bobo do que é uma stack CDK:

export class MinhaStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const bucket = new s3.Bucket(this, 'MeuBucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      encryption: s3.BucketEncryption.S3_MANAGED,
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Pronto, isso aí já cria um S3 privado com criptografia ativada. Sem clicar em lugar nenhum.

A arquitetura que a gente vai montar

O fluxo do site fica assim:

Arquitetura

Traduzindo:

  1. Usuário acessa seusite.com no navegador.
  2. Cloudflare resolve o DNS e aponta pro CloudFront.
  3. CloudFront entrega via HTTPS (cert ACM) usando OAC pra puxar o arquivo do S3 privado.
  4. S3 nunca fica público. Quem fala com ele é só o CloudFront.

E no lado do deploy:

  1. Você dá push na branch main.
  2. GitHub Actions autentica na AWS via OIDC (sem access key fixa, mais seguro).
  3. Roda cdk deploy, faz s3 sync e invalida o cache do CloudFront.

Bonito né? Bora colocar pra rodar.

O repositório

O projeto tá aqui, MIT, pode clonar à vontade:

github.com/markgerald/aws-cdk-static-site-starter

Tem versão da doc em inglês e português. Aqui no post vou resumir o caminho feliz.

Pré-requisitos

Antes de qualquer coisa, você precisa de:

  • Conta AWS (tem free tier, dá pra brincar sem pagar).
  • Node.js 22+ instalado.
  • AWS CLI configurado (aws configure).
  • Domínio na Cloudflare como DNS autoritativo.

Se você nunca instalou Node/AWS CLI, tem um tutorial passo a passo aqui: Instalação local.

Pra configurar a Cloudflare, segue esse: Domínio, DNS e SSL/TLS na Cloudflare.

Clone e configura o .env

git clone https://github.com/markgerald/aws-cdk-static-site-starter.git
cd aws-cdk-static-site-starter
cp .env.example .env
Enter fullscreen mode Exit fullscreen mode

Abre o .env no editor e ajusta. Esse é o coração da configuração:

PROJECT_NAME=meu-site
DOMAIN_NAME=meusite.com
WWW_DOMAIN_NAME=www.meusite.com

AWS_ACCOUNT_ID=123456789012
AWS_REGION=eu-west-1
CERTIFICATE_REGION=us-east-1

ENABLE_SPA_FALLBACK=true

CREATE_GITHUB_OIDC_ROLE=false
GITHUB_OWNER=seuuser
GITHUB_REPO=seu-repo
GITHUB_BRANCH=main
GITHUB_OIDC_PROVIDER_ARN=
Enter fullscreen mode Exit fullscreen mode

Atenção em dois pontos:

  • CERTIFICATE_REGION tem que ser us-east-1. CloudFront só aceita cert ACM dessa região, é regra da AWS.
  • AWS_REGION pode ser onde você quiser. Eu uso eu-west-1 por padrão, mas sa-east-1 (São Paulo) funciona igual.

Instala e faz bootstrap

npm install

# Bootstrap nas duas regiões (cert e stack principal)
npx cdk bootstrap aws://123456789012/us-east-1
npx cdk bootstrap aws://123456789012/eu-west-1
Enter fullscreen mode Exit fullscreen mode

Bootstrap é tipo um "setup inicial" que o CDK faz na conta. Cria um bucket de assets, role pra deploy, essas coisas. Roda uma vez por região e esquece.

Primeiro deploy

npm run build
npm run build:site
npm test

npx cdk deploy --all --require-approval never
Enter fullscreen mode Exit fullscreen mode

Na primeira vez, o ACM vai ficar pendente esperando validação DNS. Isso é o seguinte: a AWS precisa confirmar que o domínio é seu. Ela mostra uns CNAME bizarros pra você copiar pra Cloudflare.

Vai no console AWS > ACM > região us-east-1 > seu certificado. Ele mostra algo tipo:

Name:  _abc123.meusite.com
Value: _xyz789.acm-validations.aws
Type:  CNAME
Enter fullscreen mode Exit fullscreen mode

Copia isso pra Cloudflare, DNS only (nuvem cinza, sem proxy). Aguarda uns minutos, ACM marca Issued e o deploy segue.

Detalhes finos da validação: Tutorial Cloudflare ACM no repo.

Aponta o domínio pro CloudFront

Depois que a stack subiu, o CDK te devolve um output tipo d111111abcdef8.cloudfront.net. Vai na Cloudflare e cria:

Type   Name   Target
CNAME  @      d111111abcdef8.cloudfront.net
CNAME  www    d111111abcdef8.cloudfront.net
Enter fullscreen mode Exit fullscreen mode

Começa com DNS only. Se depois quiser ativar o proxy laranja da Cloudflare, usa modo Full (strict) no SSL/TLS. Nunca Flexible (gera loop de redirect).

Abre o navegador, acessa https://meusite.com, deve carregar a página de exemplo do repo.

Como adaptar pro seu site

Agora vem a parte que interessa: como botar seu site no lugar do exemplo.

A pasta com os arquivos do site é website_src/. Por padrão vem um React + Vite mínimo. Estrutura:

website_src/
├── favicon.svg
├── index.html
├── src/
│   └── (componentes React)
├── tsconfig.json
└── vite.config.ts
Enter fullscreen mode Exit fullscreen mode

Você tem duas opções:

Opção 1: jogar seu projeto inteiro no website_src/

Mais simples. Apaga o conteúdo de website_src/ e cola o seu projeto (Next exportado, Astro, Vue, HTML puro, qualquer coisa). Ajusta o script build:site no package.json pra rodar o build do seu framework. Hoje tá assim:

"build:site": "vite build --config website_src/vite.config.ts"
Enter fullscreen mode Exit fullscreen mode

Se você usa Astro, vira algo tipo:

"build:site": "cd website_src && npm install && npm run build"
Enter fullscreen mode Exit fullscreen mode

O importante é que o build final tem que sair em website_dist/, porque é essa pasta que o deploy faz upload pro S3. Configura o output do seu framework pra apontar pra lá:

// vite.config.ts
export default defineConfig({
  build: {
    outDir: '../website_dist',
  },
});
Enter fullscreen mode Exit fullscreen mode

Opção 2: usar como monorepo

Você mantém seu site num repositório separado, gera os arquivos, joga no website_dist/ e faz só o deploy. Útil se o time de front é separado do time de infra.

HTML puro? Beleza.

Se seu site é só HTML/CSS/JS estático, joga tudo direto em website_dist/:

mkdir -p website_dist
cp -r meu-site-pronto/* website_dist/
Enter fullscreen mode Exit fullscreen mode

E desativa o SPA fallback (não precisa de fallback pra index.html quando não tem rotas client-side):

ENABLE_SPA_FALLBACK=false
Enter fullscreen mode Exit fullscreen mode

Faz o deploy do site

Depois de configurar e buildar:

npm run build:site

BUCKET_NAME=$(aws cloudformation describe-stacks \
  --stack-name aws-cdk-static-site-starter-static-site \
  --region eu-west-1 \
  --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucketName'].OutputValue" \
  --output text)

DISTRIBUTION_ID=$(aws cloudformation describe-stacks \
  --stack-name aws-cdk-static-site-starter-static-site \
  --region eu-west-1 \
  --query "Stacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue" \
  --output text)

aws s3 sync website_dist/ "s3://${BUCKET_NAME}" --delete --cache-control "public,max-age=300"
aws cloudfront create-invalidation --distribution-id "$DISTRIBUTION_ID" --paths "/*"
Enter fullscreen mode Exit fullscreen mode

Esse create-invalidation é pra forçar o CloudFront a buscar a versão nova. Sem isso, ele pode servir cache velho por minutos/horas.

Dica: dá pra colocar isso num script deploy-site.sh e rodar de uma vez.

Automatiza tudo no GitHub Actions

Manualzão é bom pra aprender. Em produção, você quer push na main e ver o site atualizar sozinho.

O repo já vem com dois workflows:

  • .github/workflows/ci.yml: roda em PR e push, faz build/test/synth, sem credencial AWS.
  • .github/workflows/deploy.yml: roda em push na main, autentica via OIDC, faz cdk deploy, sync e invalidation.

OIDC é uma sacada legal: em vez de você guardar AWS_ACCESS_KEY_ID e AWS_SECRET_ACCESS_KEY como segredos no GitHub (que vazam em log), o GitHub Actions pede uma credencial temporária pra AWS, que confia no provedor OIDC do GitHub. Sem chave fixa, sem rotação manual.

Pra criar a role IAM com confiança OIDC, ativa no .env:

CREATE_GITHUB_OIDC_ROLE=true
GITHUB_OWNER=seuuser
GITHUB_REPO=seu-repo
GITHUB_BRANCH=main
Enter fullscreen mode Exit fullscreen mode

Roda npx cdk deploy de novo, copia o output GithubActionsRoleArn e adiciona como Repository Variable no GitHub:

AWS_ROLE_ARN=arn:aws:iam::123456789012:role/...github-actions-deploy
Enter fullscreen mode Exit fullscreen mode

Junto com as outras variables (mesmas chaves do .env):

AWS_ACCOUNT_ID=123456789012
AWS_REGION=eu-west-1
DOMAIN_NAME=meusite.com
WWW_DOMAIN_NAME=www.meusite.com
PROJECT_NAME=meu-site
CERTIFICATE_REGION=us-east-1
ENABLE_SPA_FALLBACK=true
CREATE_GITHUB_OIDC_ROLE=false
GITHUB_OWNER=seuuser
GITHUB_REPO=seu-repo
GITHUB_BRANCH=main
Enter fullscreen mode Exit fullscreen mode

Aí dá push na main e fica esperto no Actions: build, deploy, sync, invalidation. Tudo automático.

Quanto custa essa brincadeira?

Pra um site pequeno (até alguns milhares de hits por mês), o custo fica centavos por mês ou zerado, dependendo do volume:

  • ACM: certificado público pra CloudFront é gratuito.
  • S3: cobra storage e requests. Site pequeno fica em centavos.
  • CloudFront: tem free tier mensal generoso (1 TB transferência + 10M requests). Acima disso, paga.
  • Cloudflare DNS: plano free resolve.

O que pode encarecer:

  • Tráfego alto no CloudFront (acima do free tier).
  • Invalidations frequentes e amplas (/* direto, várias vezes por dia).
  • Habilitar WAF, logs, Lambda@Edge, CloudFront Functions sem necessidade.

Por isso o projeto propositalmente não liga essas coisas. Se você precisar, liga depois, com consciência do custo.

Segurança, o feijão com arroz

O setup já vem com:

  • S3 com BlockPublicAccess. Nunca exposto.
  • CloudFront acessa S3 via OAC (a forma moderna), não OAI legado.
  • HTTP redireciona pra HTTPS automaticamente.
  • Workflow usa OIDC, sem access key long-lived.

Em produção, troca o AdministratorAccess da role de deploy por uma política de menor privilégio. O readme explica como.

E a parte de "deletar tudo"?

Quer derrubar a brincadeira? Esvazia o bucket primeiro (o autoDeleteObjectsfalse de propósito, pra evitar custom resources):

BUCKET_NAME=$(aws cloudformation describe-stacks \
  --stack-name aws-cdk-static-site-starter-static-site \
  --region eu-west-1 \
  --query "Stacks[0].Outputs[?OutputKey=='WebsiteBucketName'].OutputValue" \
  --output text)

aws s3 rm "s3://${BUCKET_NAME}" --recursive
npx cdk destroy --all
Enter fullscreen mode Exit fullscreen mode

Não esquece de apagar os CNAMEs na Cloudflare também.

Pra fechar

Resumindo o que rolou:

  • Você aprendeu (ou revisou) AWS, IaC e CDK.
  • Subiu um site estático na AWS com HTTPS, CDN global e DNS na Cloudflare.
  • Configurou deploy automático via GitHub Actions com OIDC.
  • Pagou praticamente nada por isso.

O projeto é open source, tá no github.com/markgerald/aws-cdk-static-site-starter. Se curtir, deixa uma star. Se achar bug ou tiver sugestão, manda PR ou abre issue.

Dúvida? Comenta aí embaixo que eu respondo.

Valeu, falou! 👋


Links úteis

Top comments (0)