TL;DR — Resumo Rápido
Guia de GitHub Actions: automatize pipelines com proteção de ambientes, segredos, múltiplos destinos, cache de dependências e estratégias de rollback.
Fazer deploy manualmente — conectar via SSH a um servidor, rodar comandos de build, torcer para dar certo — é propenso a erros, inauditável e não escala. GitHub Actions permite definir todo o pipeline de implantação como código: lint, testes, build, aprovação, deploy e notificação — tudo disparado automaticamente a cada push para main. Este guia cobre a criação de um workflow de implantação automatizado para produção, desde o primeiro arquivo YAML até deploys multi-destino, proteção de ambientes e estratégias de rollback.
Pré-requisitos
Antes de criar seu workflow de deploy, tenha o seguinte em ordem:
- Um repositório no GitHub com código de aplicação (exemplos em Node.js ao longo do guia)
- Um ambiente destino — conta Cloudflare Workers, um VPS com acesso SSH, ou um runner auto-hospedado em Windows para IIS
- Familiaridade básica com YAML — workflows do GitHub Actions são arquivos YAML onde a indentação importa
- Acesso às configurações do repositório no GitHub para armazenar segredos e criar ambientes
Conceitos Básicos do GitHub Actions
Um workflow do GitHub Actions é um arquivo YAML armazenado em .github/workflows/. É disparado por um ou mais eventos (push, pull request, agendamento, disparo manual). Um workflow contém um ou mais jobs. Cada job roda em um runner — uma máquina virtual hospedada pelo GitHub (Ubuntu, Windows, macOS) ou auto-hospedada. Cada job contém steps sequenciais, cada um executando um comando shell (run) ou uma unidade reutilizável chamada action (uses).
Os blocos fundamentais:
name: Deploy # nome do workflow na aba Actions
on: # gatilho
push:
branches: [main]
jobs:
deploy: # ID do job
runs-on: ubuntu-latest # runner
steps:
- uses: actions/checkout@v4 # action (baixa o código-fonte)
- run: npm ci # comando shell
- run: npm run build
Jobs rodam em paralelo por padrão. Use needs: [job-id] para impor uma ordem.
Seu Primeiro Workflow de Implantação
Aqui está um workflow completo para um projeto Node.js que faz deploy no Cloudflare Workers usando Wrangler. Primeiro executa lint e testes, depois faz build e por fim faz deploy — apenas em pushes para main.
name: Deploy no Cloudflare Workers
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm run lint
- run: npm test
build:
needs: lint-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy com Wrangler
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Notificar sucesso no Slack
if: success()
run: |
curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H "Content-Type: application/json" \
-d "{\"text\":\"Deploy de ${{ github.sha }} em produção por ${{ github.actor }}\"}"
O bloco concurrency no topo evita que dois deploys se sobreponham. Se você fizer push duas vezes rapidamente, a primeira execução é cancelada antes que a segunda comece.
Regras de Proteção de Ambiente
Fazer push diretamente para produção sem uma verificação humana é arriscado para sistemas críticos. Os ambientes do GitHub permitem adicionar regras de proteção a qualquer job que os aponte.
Para configurar a proteção de ambiente:
- Vá para Settings → Environments no seu repositório
- Clique em New environment e nomeie-o
production - Ative Required reviewers — adicione um ou mais usuários ou equipes do GitHub
- Opcionalmente defina um Wait timer (por exemplo, 10 minutos) antes que o job possa executar
- Opcionalmente restrinja quais branches podem fazer deploy neste ambiente (por exemplo, apenas
main)
Uma vez configurado, qualquer job com environment: production pausará e aguardará a aprovação de um revisor antes de prosseguir. O revisor vê o deploy pendente na UI do GitHub e pode aprovar ou rejeitar com um comentário.
Gerenciamento de Segredos
Nunca escreva tokens de API, chaves SSH ou senhas nos arquivos de workflow. O GitHub oferece três níveis de armazenamento de segredos:
Segredos de repositório — Disponíveis para todos os workflows do repo. Vá para Settings → Secrets and variables → Actions → New repository secret.
Segredos de ambiente — Limitados a um ambiente específico (por exemplo, production). Só são expostos quando um job aponta para esse ambiente.
Segredos de organização — Compartilhados entre múltiplos repositórios de uma organização do GitHub, ideais para tokens usados por muitos projetos.
Referencie segredos nos workflows com a sintaxe ${{ secrets.NOME_SEGREDO }}:
- name: Deploy com Wrangler
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
OIDC (OpenID Connect) é uma alternativa a segredos de longa duração para provedores de nuvem que o suportam (AWS, GCP, Azure). Em vez de armazenar um token estático, o runner solicita uma credencial de curta duração ao provedor em tempo de execução.
Deploy em Diferentes Destinos
Cloudflare Workers com Wrangler
A action cloudflare/wrangler-action cuida da autenticação e executa qualquer comando Wrangler:
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: deploy --env production
VPS via SSH e rsync
Para um VPS Linux, use SSH para conectar e rsync para transferir arquivos:
- name: Deploy no VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.DEPLOY_HOST }}
username: ${{ secrets.DEPLOY_USER }}
key: ${{ secrets.DEPLOY_SSH_KEY }}
script: |
rsync -az --delete dist/ /var/www/myapp/dist/
systemctl restart myapp
Armazene a chave SSH privada nos segredos do GitHub. Adicione a chave pública correspondente a ~/.ssh/authorized_keys no servidor. Use um usuário de deploy dedicado com permissões mínimas — nunca root.
IIS via Runner Auto-Hospedado
Para deploys no Windows IIS, execute um runner auto-hospedado diretamente no servidor:
deploy-iis:
needs: build
runs-on: [self-hosted, iis-deploy]
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: C:\inetpub\wwwroot\myapp
- name: Reiniciar site IIS
run: |
Import-Module WebAdministration
Restart-WebItem 'IIS:\Sites\MyApp'
shell: powershell
Cache e Performance
Cache de Dependências
Re-baixar pacotes npm em cada execução desperdiça tempo. Faça cache deles:
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
Para .NET:
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj') }}
restore-keys: ${{ runner.os }}-nuget-
Grupos de Concorrência
Grupos de concorrência evitam deploys em paralelo — uma fonte comum de race conditions:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Comparativo: GitHub Actions vs GitLab CI vs Jenkins
| Recurso | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| Hospedagem | GitHub (SaaS) | GitLab ou auto-hospedado | Apenas auto-hospedado |
| Arquivo de config | .github/workflows/*.yml | .gitlab-ci.yml | Jenkinsfile (Groovy) |
| Plano gratuito | 2.000 min/mês (privado) | 400 min/mês (privado) | Ilimitado (sua infra) |
| Marketplace | 20.000+ actions | Limitado | 1.800+ plugins |
| Gates de ambiente | Nativos (grátis para público) | Nativos (Ultimate para regras) | Via plugins |
| OIDC | AWS, GCP, Azure, Cloudflare | AWS, GCP, Azure | Via plugins |
| Curva de aprendizado | Baixa | Baixa | Alta (DSL Groovy) |
| Ideal para | Projetos no GitHub | Monorepos no GitLab | Pipelines legados complexos |
Cenário Real: Pipeline Completo
Um pipeline de produção para Node.js com notificações Slack:
name: Deploy Produção
on:
push:
branches: [main]
concurrency:
group: production-deploy
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm run lint
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm test -- --coverage
build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist-${{ github.sha }}
path: dist/
retention-days: 14
deploy:
needs: build
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist-${{ github.sha }}
path: dist/
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
- name: Notificar sucesso
if: success()
run: |
curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-d "{\"text\":\"Deploy de ${{ github.sha }} por ${{ github.actor }}\"}"
- name: Notificar falha
if: failure()
run: |
curl -s -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-d "{\"text\":\"FALHA no deploy para ${{ github.sha }}\"}"
Badges de Status
Adicione um badge de status ao vivo ao seu README:

Casos Extremos e Problemas Comuns
O mascaramento de segredos só funciona para correspondências exatas. O GitHub mascara o valor literal do segredo nos logs, mas se o segredo for codificado em base64 ou URL antes do uso, o valor original pode aparecer sem máscara.
Mudanças na imagem do runner quebram versões de ferramentas ancoradas. O GitHub atualiza ubuntu-latest periodicamente. Se seu workflow depende de uma versão específica de uma ferramenta pré-instalada, instale-a explicitamente com uma action de setup.
cancel-in-progress: true cancela a execução atual, não a que está chegando. O novo push vence. Isso é quase sempre o que você quer para deploys.
Segredos de ambiente não estão disponíveis para pull requests de forks. Isso é intencional — forks não podem acessar segredos para evitar vazamentos.
Solução de Problemas
O workflow não dispara no push para main. Verifique o filtro on.push.branches — o nome da branch deve corresponder exatamente. Confirme que o arquivo de workflow está em .github/workflows/.
O segredo está disponível mas a autenticação do Wrangler falha. Confirme que o nome do segredo corresponde exatamente (sensível a maiúsculas). Verifique que o token da API do Cloudflare tem as permissões corretas: Workers Scripts: Edit e Account: Read.
O job aguarda indefinidamente a aprovação do ambiente. Verifique que você está listado como revisor obrigatório do ambiente. Confirme que o nome do ambiente no YAML corresponde exatamente ao nome em Settings.
Resumo
- Defina workflows como arquivos YAML em
.github/workflows/— eles são versionados junto com seu código - Estruture pipelines como jobs sequenciais: lint → test → build → deploy, usando
needspara impor ordem - Use
environment: productioncom revisores obrigatórios para gates de aprovação humana - Armazene todos os segredos no armazenamento de segredos do GitHub — nunca no arquivo YAML
- Deploys no Cloudflare Workers usam
cloudflare/wrangler-action; destinos VPS usam SSH/rsync; IIS usa runner auto-hospedado - Adicione grupos de
concurrencycomcancel-in-progress: truepara evitar deploys sobrepostos - Faça cache de dependências com
actions/setup-node cache: npmpara execuções mais rápidas - Nomeie artefatos com
${{ github.sha }}para facilitar rollback re-executando o workflow em qualquer commit anterior