Configurar deploy manual funciona enquanto o time é pequeno. Na terceira semana, alguém esquece de rodar o build antes de subir a imagem. Na sexta semana, um hotfix de madrugada foi aplicado na branch errada. Automatizar CI/CD não é luxo, é algo que você deveria ter feito no dia um.
Este post cobre um workflow de GitHub Actions que constrói a imagem Docker da sua aplicação e publica na Guara Cloud a cada push na branch principal. Sem ferramentas pagas adicionais, sem YAML de 400 linhas.
Resposta rápida
Você precisa de um arquivo .github/workflows/deploy.yml com quatro etapas: checkout do código, build da imagem Docker no GitHub Actions, push da imagem para o registry da Guara Cloud, e deploy via API. Os secrets (token de API e URL do registry) ficam nas configurações do repositório. O pipeline roda a cada push na branch main e em pull requests roda apenas o build para validar que a imagem compila.
Principais pontos
- O GitHub Actions oferece 2.000 minutos grátis por mês em repositórios privados. Suficiente para times pequenos.
- Build da imagem no runner do Actions, não na máquina de produção. A Guara Cloud recebe a imagem pronta.
- Use
docker/build-push-actioncom cache do GitHub para builds subsequentes rodarem em segundos. - Separe os workflows: um para validar (PR) e outro para deploy (merge na main).
- Nunca coloque token de API ou senha de registry em texto no YAML. Use
${{ secrets.NOME }}sempre.
Quando este fluxo se aplica
Este setup funciona bem para aplicações web (Node.js, Next.js, NestJS, Python, Go) que rodam em container e usam GitHub como repositório. Se o time tem entre 1 e 10 desenvolvedores e faz deploys diários ou semanais, a automação via Actions cobre o caso inteiro. Funciona tanto para staging quanto para produção se você usar branches diferentes.
Quando não usar este fluxo
Se o repositório é muito grande (monorepo de 2GB+), o tempo de checkout no Actions começa a doer. Nesse caso, vale investigar sparse checkout ou CI local. Se a empresa exige self-hosted runners por compliance, o YAML muda pouco mas a infraestrutura muda bastante. Para projetos que não usam container (deploy de arquivos estáticos, por example), o fluxo é diferente e mais simples.
Antes de começar
- Repositório no GitHub com código da aplicação
- Dockerfile funcional na raiz do projeto
- Conta ativa na Guara Cloud com um serviço já criado
- Token de API da Guara Cloud (gerado no painel de configurações)
- Permissão de admin no repositório para adicionar secrets
1. Crie o token de API e configure os secrets
Na Guara Cloud, vá em Configurações > API Tokens e gere um token com escopo de deploy. Copie o token. No GitHub, abra o repositório, vá em Settings > Secrets and variables > Actions e adicione:
Secrets necessários no GitHub
| Secret | Descrição |
|---|---|
GUARA_API_TOKEN | Token de API gerado no painel da Guara Cloud |
GUARA_REGISTRY_URL | URL do registry de containers (ex: registry.guaracloud.com) |
GUARA_SERVICE_ID | ID do serviço onde o deploy será feito |
Não compartilhe esses valores em issues, PRs ou logs de workflow. O GitHub mascara automaticamente os valores de secrets nos logs, mas prevenir nunca é demais.
2. O workflow de validação (para pull requests)
Este arquivo roda em todo PR. Ele constrói a imagem Docker para confirmar que o Dockerfile não quebrou, mas não faz push nem deploy.
name: Validate
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
tags: test-build:pr-${{ github.event.pull_request.number }}
cache-from: type=gha
cache-to: type=gha,mode=max Se o build falhar aqui, o PR fica vermelho e ninguém consegue mergear. Isso evita imagem quebrada na main.
3. O workflow de deploy (para merge na main)
Este é o arquivo que faz o trabalho real. Ele roda quando algo chega na main, geralmente via merge de um PR aprovado.
name: Deploy
on:
push:
branches: [main]
env:
IMAGE_TAG: ${{ github.sha }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Login no registry da Guara Cloud
uses: docker/login-action@v3
with:
registry: ${{ secrets.GUARA_REGISTRY_URL }}
username: api
password: ${{ secrets.GUARA_API_TOKEN }}
- name: Build e push da imagem
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ secrets.GUARA_REGISTRY_URL }}/${{ secrets.GUARA_SERVICE_ID }}:${{ env.IMAGE_TAG }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy na Guara Cloud
run: |
curl -X POST https://api.guaracloud.com/v1/services/${{ secrets.GUARA_SERVICE_ID }}/deploy \
-H "Authorization: Bearer ${{ secrets.GUARA_API_TOKEN }}" \
-H "Content-Type: application/json" \
-d '{"image_tag": "'${{ env.IMAGE_TAG }}'"}'
shell: bash Cada deploy usa o SHA do commit como tag da imagem. Isso facilita rollback depois, porque a tag corresponde exatamente ao commit que foi deployed.
4. Reduza o tempo de build com cache
Sem cache, o docker build reinstala todas as dependências a cada pipeline. Com cache do GitHub Actions, camadas que não mudaram são reaproveitadas.
A diferença é real. Em um projeto Node.js típico, o build sem cache leva 3 a 4 minutos. Com cache ativado, cai para 40 a 60 segundos quando só mudou código de aplicação.
As linhas responsáveis por isso no YAML acima são:
cache-from: type=gha
cache-to: type=gha,mode=max
O mode=max exporta todas as camadas, não apenas as do estágio final. Em Dockerfiles multi-stage, isso faz diferença porque o estágio de build (que instala npm install) também é cacheado.
5. Notificações de deploy
Ver pipeline falhar sem saber é ruim. Adicione notificação no Slack ou Discord como última etapa do workflow:
- name: Notificar Slack
if: always()
uses: slackapi/slack-github-action@v2
with:
webhook-url: ${{ secrets.SLACK_WEBHOOK }}
payload: |
{
"text": "Deploy ${{ job.status }}: ${{ github.repository }}@${{ github.sha }}"
} O if: always() garante que a notificação dispare mesmo quando o deploy falha. Sem isso, você só fica sabendo quando entra no GitHub e olha.
6. Validações antes do merge
O workflow de validação (passo 2) pode ser expandido. Adicionar testes e lint no mesmo job garante que o PR não merge com falha:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test
- name: Build Docker image
uses: docker/build-push-action@v5
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max Configure a branch protection rule na main para exigir que o job validate passe antes de permitir merge. Isso evita deploy de código quebrado.
Problemas comuns
- Problema O docker login falha com 401 Unauthorized
- Como resolver Verifique se o token de API tem escopo de deploy e se o secret GUARA_API_TOKEN está configurado corretamente. Tokens expiram, então gere um novo se necessário.
- Problema Build da imagem leva mais de 10 minutos
- Como resolver Ative o cache GHA no build-push-action. Se já está ativo, verifique se o Dockerfile ordena as camadas corretamente: dependências primeiro, código por último.
- Problema O deploy é acionado em push para qualquer branch
- Como resolver Confirme que o trigger on.push.branches lista apenas [main]. Verificações com glob patterns como branches: ["*"] acionam em qualquer branch.
- Problema A curl de deploy retorna 404
- Como resolver O GUARA_SERVICE_ID pode estar errado. Verifique no painel da Guara Cloud e confirme que o serviço existe na sua conta.
- Problema Logs mostram "layer not found" no cache
- Como resolver O cache do GHA tem limite de tamanho. Se o projeto gera muitas camadas, considere usar cache registry (type=registry) em vez de GHA.
Posso usar este fluxo com monorepos (Nx, Turborepo)?
Sim, mas ajuste o context do build-push-action para apontar para o diretório da aplicação específica. Use também paths-filter no trigger para o workflow só rodar quando arquivos daquela aplicação mudarem.
Quanto custa os minutos de GitHub Actions?
Repositórios públicos têm minutos ilimitados. Privados incluem 2.000 minutos por mês no plano Free, 3.000 no Pro e 10.000 no Team. Um build de 5 minutos por deploy, com 20 deploys no mês, consome 100 minutos.
Preciso pagar algo a mais na Guara Cloud pelo CI/CD?
Não. O registry e a API de deploy são parte do plano. Você paga apenas pelo tempo de execução do container, igual ao deploy manual.
Como faço rollback se o deploy automático quebrou a produção?
Cada deploy usa o SHA do commit como tag. No painel da Guara Cloud, selecione a versão anterior e clique em Rollback. Ou reverta o commit na main e o pipeline fará o deploy da versão corrigida automaticamente.
Posso ter ambientes de staging e production no mesmo pipeline?
Sim. Adicione um job separado de staging que roda primeiro, e o job de production pode depender dele com needs: [staging]. Use branches diferentes (develop para staging, main para production).
O que fazer a seguir
Com o pipeline rodando, o próximo passo costuma ser adicionar testes de integração na etapa de validação e configurar preview deployments para cada PR. A Guara Cloud suporta serviços temporários que você pode subir e destruir automaticamente.
Faça deploy da sua aplicação no Brasil
Containers com HTTPS, domínio público e cobrança em Real. Sem surpresas na fatura.