03 - GitHub Actions
video: https://youtu.be/... no frontmatter da nota.GitHub Actions
GitHub Actions é o sistema de integração e entrega contínua nativo do GitHub, no qual workflows escritos em YAML reagem a eventos do repositório, push, pull request, tag, agendamento ou despacho manual, e executam jobs em runners hospedados ou próprios. A proposta é transformar a automação em mais um artefato do repositório: versionada, revisada, reutilizável e distribuída junto com o código. O marketplace de actions amplia o modelo com milhares de componentes prontos, do checkout ao deploy em qualquer nuvem, e os recursos de cache, artefatos e matrix builds cobrem o repertório completo de um pipeline de produção. Em serviços na internet, a combinação de baixo atrito inicial, execução gerenciada e integração profunda com o fluxo de pull request fez da ferramenta o padrão de fato para times que já hospedam código no GitHub.
Conceito e modelo de execução
O modelo de execução tem quatro níveis hierárquicos: evento, workflow, job e step. O evento dispara um workflow definido em .github/workflows; o workflow agrupa jobs que rodam em paralelo por padrão, cada um em um runner isolado; o job contém steps, que são comandos ou actions reutilizáveis; e os steps compartilham o mesmo filesystem dentro do job. O resultado aparece no commit, no pull request e nas checks de proteção de branch, fechando o ciclo de validação no próprio fluxo de revisão.
- Evento: gatilho que inicia o workflow; pode ser push, pull_request, schedule, workflow_dispatch ou dezenas de eventos específicos da plataforma.
- Workflow: arquivo YAML versionado em .github/workflows, com gatilhos, permissões, jobs e steps declarados de forma explícita.
- Job: unidade de execução em um runner isolado; jobs independentes rodam em paralelo, e needs estabelece dependências entre eles.
- Action: pacote reutilizável consumido por uses, que encapsula um passo inteiro, como autenticar na nuvem, configurar um runtime ou publicar um artefato.
O isolamento por job é uma decisão de desenho relevante: cada job recebe um runner limpo, o que elimina contaminação entre execuções, ao custo de exigir mecanismos explícitos de cache e artifacts para compartilhar dados entre jobs.
Dois recursos complementares completam o modelo. Os grupos de concorrência permitem cancelar automaticamente a execução em curso de um branch quando um novo push chega, com a chave concurrency e a opção cancel-in-progress, o que economiza runners e evita pilha de resultados obsoletos. Os environments nomeados agrupam segredos, regras de aprovação e restrições por branch, funcionando como fronteira declarativa entre o deploy automatizado de homologação e o deploy de produção com revisão humana.
O problema que resolve
O GitHub Actions resolve o custo de montar e manter infraestrutura de CI para times que não querem operar servidores de automação. Antes das plataformas gerenciadas, adotar CI significava provisionar máquina, instalar agente, renovar credenciais, escalar capacidade e aplicar patches; com a ferramenta, o time descreve o pipeline em YAML e a plataforma cuida do resto. O segundo problema atacado é o da distribuição de automação: scripts de release comuns a vários projetos, que antes se copiavam manualmente, viram actions compostas versionadas, consumidas por referência e atualizadas de forma controlada. Em produção, esse modelo transformou a forma como times pequenos entregam, porque entrega contínua deixa de ser projeto de infraestrutura e passa a ser arquivo de configuração.
Anatomia de um workflow
O exemplo abaixo concentra os recursos que aparecem na prática diária: permissões mínimas, matriz de build, cache de dependências e autenticação em nuvem sem segredo estático:
name: ci-cd
on:
push:
branches: [main] # valida o tronco a cada commit
pull_request: # valida antes de integrar
permissions:
contents: read # privilégio mínimo por padrão
id-token: write # permite emissão de token OIDC
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: true # primeira falha cancela os irmãos
matrix:
node: [18, 20, 22] # três versões em runners separados
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- run: npm ci
- run: npm test -- --coverage
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC para autenticação na nuvem
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-ci
aws-region: sa-east-1
- run: ./deploy.sh prod ${{ github.sha }}
Matrix builds
A matriz executa o mesmo conjunto de steps sob combinações diferentes, versões de runtime, sistemas operacionais ou flags de configuração, cada combinação em um runner independente. O caso de uso clássico é a biblioteca que precisa garantir compatibilidade com três versões de linguagem; o caso mais frequente em serviços é validar o build em duas arquiteturas de CPU antes de publicar a imagem. O fail-fast controlado é a diferença entre matriz profissional e desperdício: cancelar irmãos na primeira falha economiza minutos de execução, enquanto permitir que todos os ramos completem ajuda a mapear o alcance de uma quebra. Variáveis derivadas como include e exclude permitem tratar exceções sem duplicar o workflow, e é essa capacidade que mantém a matriz legível quando ela cresce.
A regra de dimensionamento prática é evitar matrizes explosivas, aquelas que multiplicam três runtimes por dois sistemas por quatro regiões e consomem a concorrência da organização inteira em um único push. Matriz existe para cobrir risco real de incompatibilidade; combinações sem decisão associada são custo permanente sem retorno, e o corte periódico da matriz é manutenção tão importante quanto sua criação.
Runners hospedados e self-hosted
Os runners hospedados pela plataforma dispensam qualquer infraestrutura e atendem a maioria dos cenários, com limites de tempo e concorrência por plano. Os runners self-hosted existem para os casos em que a nuvem da plataforma não alcança: redes privadas, hardware específico, cargas intensivas e requisitos de residência de dados. O avanço recente mais relevante nessa frente é o runner scale sets, mecanismo de auto scaling para runners self-hosted em Kubernetes, no qual um conjunto de escala observa a fila e provisiona pods de execução sob demanda, eliminando a antiga escolha entre runner permanente e fila infinita. A operação de self-hosted exige disciplina que a versão hospedada não cobra:
- Versão mínima atualizada do runner: versões antigas perdem suporte e deixam de receber correções de segurança; a atualização precisa entrar na rotina de manutenção.
- Runners efêmeros: cada execução em um runner recém-criado e destruído no fim, eliminando o estado residual que produz falha intermitente.
- Isolamento por repositório: runner registrado para um repositório privado não deve atender a forks públicos, porque pull requests externos executam código não confiável.
- Capacidade monitorada: fila e concorrência observadas com o mesmo rigor de qualquer serviço, porque runner saturado aparece como latência silenciosa no feedback do time.
Segurança: secrets, OIDC e supply chain
O modelo de segurança da ferramenta tem três camadas que precisam ser usadas juntas. As secrets por repositório, ambiente e organização armazenam credenciais sem expor valores nos logs. A federação OIDC substitui o segredo estático de longa duração por tokens temporários emitidos a cada execução, o que remove a classe inteira de incidentes com chaves vazadas de repositório. E a governança de supply chain cuida do que o workflow executa: actions de terceiros são dependências de código com poder de acesso a segredos e ao checkout. O desenho de autenticação em nuvem por OIDC tem a forma abaixo:
permissions:
id-token: write # autoriza emissão do token OIDC na execução
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-ci
aws-region: sa-east-1
# a política de confiança do role aceita apenas o repositório
# e o workflow específicos, limitando o raio de comprometimento
As práticas essenciais da governança de supply chain:
- Pinning de versão: consumir actions por SHA de commit completo, e não por tag móvel, garantindo que a atualização seja decisão revisada, não evento remoto.
- Permissão mínima no workflow: o bloco permissions restringe o token de execução ao estritamente necessário, job por job.
- Revisão de dependências de terceiros: avaliar a procedência e a manutenção de cada action antes de adotar, tratando a escolha como revisão de biblioteca.
- Ambientes com aprovação: deploys para produção em environments com revisores exigidos, ligando o gate humano ao mecanismo nativo da plataforma.
Cache e artifacts
Cache e artifacts respondem a necessidades diferentes e costumam ser confundidos. O cache guarda dados intermediários para acelerar execuções futuras, dependências de pacotes e builds incrementais, com chave derivada de hash de lockfile e validade automática. O artifact guarda saída que precisa sobreviver à execução, relatório de cobertura, log de testes, imagem empacotada, para download posterior ou consumo por outro job via upload-artifact e download-artifact. Em pipelines com jobs isolados, os artifacts são a ponte natural entre build e deploy, e o hábito saudável é definir retenção explícita, porque armazenamento acumulado sem política vira custo silencioso.
A distinção tem consequência de segurança que merece destaque: cache pode ser lido por execuções de outros branches do mesmo repositório, e artifacts seguem as permissões do repositório. Publicar segredos ou dados de cliente em qualquer um dos dois mecanismos transforma o repositório em cofre indevido, acessível por qualquer pessoa com permissão de leitura da ação. A regra prática é simples, cache e artifact transportam dados derivados do código, nunca credenciais, e a revisão do workflow deve questionar qualquer passo que publique conteúdo além de relatórios e binários.
Comparações: GitHub Actions versus GitLab CI e Jenkins
| Aspecto | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| Configuração | Workflow em YAML por evento | .gitlab-ci.yml por projeto | Jenkinsfile em Groovy |
| Execução gerenciada | Runners hospedados por padrão | Runners compartilhados no SaaS | Requer servidor próprio |
| Reuso de automação | Actions compostas e workflows reutilizáveis | Includes e templates hierárquicos | Shared libraries em Groovy |
| Autenticação em nuvem | OIDC nativo sem segredo estático | Variáveis CI/CD com máscara | Credenciais do controller |
| Escala self-hosted | Runner scale sets em Kubernetes | Runners próprios ou Kubernetes | Agents estáticos ou efêmeros |
| Risco de supply chain | Marketplace amplo exige pinning | Templates oficiais mais fechados | Plugins com ciclo de segurança próprio |
A leitura comparativa objetiva é direta: o GitHub Actions ganha quando o código já vive no GitHub e o time quer entrega sem infraestrutura; o GitLab CI mantém vantagem em ambientes e review apps nativos; o Jenkins permanece soberano onde o runtime precisa ser controlado ao detalhe.
Erros comuns e boas práticas
Os erros abaixo aparecem com regularidade e têm correção conhecida:
- Consumo de actions por tag móvel: uma action atualizada a montante pode introduzir comportamento hostil ou quebrado a montante; pinning por SHA e atualização programada resolvem.
- Segredos estáticos onde caberia OIDC: credenciais de nuvem gravadas em secrets expiram, vazam e geram rotação manual; federação OIDC elimina a classe de problema.
- Workflows com permissions amplas: token de execução com acesso de escrita ao repositório inteiro amplia o estrago de qualquer comprometimento; permissão mínima por job é o padrão.
- Runner self-hosted exposto a pull requests públicos: execução de código não confiável em runner com acesso a rede interna é vetor clássico de comprometimento; isolamento por repositório e runners efêmeros são obrigatórios.
- Fila ignorada até doer: concorrência insuficiente aparece como lentidão crescente no feedback; monitorar duração e fila evita a degradação silenciosa do hábito de integrar.
- Execução sem limite de tempo: workflow sem timeout depende de limites da plataforma e pode consumir minutos de cobrança em loop de teste travado; timeout por job é configuração de uma linha.
Entre as boas práticas que sustentam a ferramenta em produção, três se destacam. Tratar o workflow como código de produção, com revisão obrigatória de mudanças e proteção de branch sobre os arquivos de pipeline. Centralizar a automação comum em actions compostas da organização, atualizadas por dependabot com revisão humana, para que segurança e padrão se propaguem sem cópia manual. E revisar o inventário de workflows a cada trimestre, removendo gatilhos redundantes e execuções duplicadas, porque pipelines que ninguém mais entende são a forma mais barata de desperdício recorrente.