GitHub Actions: Sua Primeira Automação de CI/CD

[64] GitHub Actions: Sua Primeira Automação de CI/CD

GitHub Actions do zero: os conceitos de workflow, event, job e step, a estrutura do YAML em .github/workflows, jobs em paralelo e dependências com needs, matriz de versões, uso seguro de secrets e disparos manual e agendado.
DevOps

11 min de leitura

Até aqui o repositório Git funcionou como um arquivo histórico — um lugar onde mudanças são registradas. O GitHub Actions transforma esse repositório em algo ativo: cada evento que acontece nele pode disparar uma sequência de ações automatizadas.

Um push na branch main pode rodar os testes, construir a aplicação e fazer o deploy em produção — tudo sem intervenção humana. Um Pull Request aberto pode verificar a qualidade do código antes que qualquer revisor humano o leia. Uma tag criada pode gerar um pacote e publicá-lo automaticamente.

Esse é o conceito de CI/CD em sua forma mais concreta, e o GitHub Actions é hoje uma das ferramentas mais acessíveis para implementá-lo.

Conceitos Fundamentais

Antes de escrever o primeiro workflow, é necessário entender quatro conceitos que aparecem em toda configuração do GitHub Actions:

Workflow — o arquivo de automação em si, escrito em YAML e armazenado em .github/workflows/. Um repositório pode ter múltiplos workflows.

Event — o gatilho que dispara o workflow. Pode ser um push, um pull request, um agendamento via cron, ou até um disparo manual.

Job — uma unidade de trabalho dentro do workflow. Cada job roda em uma máquina virtual isolada. Jobs podem rodar em paralelo ou em sequência, com dependências declaradas.

Step — cada passo dentro de um job. Um step pode executar um comando shell ou usar uma action pré-construída.

Estrutura de um Arquivo de Workflow

name: Nome do Workflow

on:                        # evento que dispara o workflow
  push:
    branches: [ main ]

jobs:
  nome-do-job:             # identificador do job
    runs-on: ubuntu-latest # sistema operacional da máquina virtual

    steps:
      - name: Descrição do step
        run: echo "Olá, mundo"

Os arquivos ficam obrigatoriamente em .github/workflows/ na raiz do repositório. O nome do arquivo é livre, mas deve ter extensão .yml ou .yaml.

O Primeiro Workflow: Verificação Básica

Cria-se o diretório e o arquivo:

mkdir -p .github/workflows
touch .github/workflows/ci.yml

Conteúdo do arquivo:

name: CI — Verificação Básica

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]

jobs:
  verificar:
    runs-on: ubuntu-latest

    steps:
      - name: Clona o repositório
        uses: actions/checkout@v4

      - name: Exibe informações do ambiente
        run: |
          echo "Branch: ${{ github.ref_name }}"
          echo "Commit: ${{ github.sha }}"
          echo "Autor: ${{ github.actor }}"
          uname -a

      - name: Verifica arquivos presentes
        run: ls -la

Ao fazer o push desse arquivo, o GitHub executa o workflow automaticamente. O resultado pode ser acompanhado na aba Actions do repositório.

Workflows com Múltiplos Jobs

Jobs diferentes podem rodar em paralelo, reduzindo o tempo total do pipeline:

name: CI — Build e Testes

on:
  push:
    branches: [ main ]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Verifica formatação
        run: echo "Rodando linter..."

  testes-unitarios:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Roda testes unitários
        run: echo "Rodando testes..."

  testes-integracao:
    runs-on: ubuntu-latest
    needs: testes-unitarios    # só roda após testes-unitarios concluir
    steps:
      - uses: actions/checkout@v4
      - name: Roda testes de integração
        run: echo "Rodando testes de integração..."

O campo needs declara dependências entre jobs. No exemplo acima, lint e testes-unitarios rodam em paralelo, enquanto testes-integracao aguarda testes-unitarios terminar com sucesso.

Usando Actions Pré-Construídas

O GitHub Actions possui um marketplace com milhares de actions criadas pela comunidade. As mais usadas são oficiais e mantidas pelo próprio GitHub.

Um workflow real para uma aplicação Node.js:

name: CI — Node.js

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-e-teste:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]   # testa em múltiplas versões

    steps:
      - name: Clona o repositório
        uses: actions/checkout@v4

      - name: Configura Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - name: Instala dependências
        run: npm ci

      - name: Roda os testes
        run: npm test

      - name: Faz o build
        run: npm run build

O bloco strategy.matrix instrui o GitHub Actions a rodar o job múltiplas vezes, uma para cada valor da matriz. No exemplo, o pipeline roda duas vezes — uma com Node 18 e outra com Node 20 — em paralelo.

Variáveis e Secrets

Informações sensíveis — senhas, tokens, chaves de API — nunca devem ser escritas diretamente no arquivo de workflow. O GitHub oferece Secrets: variáveis criptografadas configuradas nas configurações do repositório (Settings → Secrets and variables → Actions).

Usando secrets no workflow:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy no servidor
        env:
          SERVIDOR: ${{ secrets.SERVIDOR_HOST }}
          USUARIO: ${{ secrets.SERVIDOR_USUARIO }}
          CHAVE_SSH: ${{ secrets.CHAVE_SSH_PRIVADA }}
        run: |
          echo "$CHAVE_SSH" > chave.pem
          chmod 600 chave.pem
          ssh -i chave.pem -o StrictHostKeyChecking=no \
            $USUARIO@$SERVIDOR "cd /app && git pull && npm install"

O valor de um secret nunca aparece nos logs — o GitHub Actions o mascara automaticamente.

Além da interface do GitHub, secrets podem ser gravados pela linha de comando com o GitHub CLI — útil em um Codespace, em scripts de provisionamento, ou quando o valor é grande demais para colar à mão:

# Pede o valor de forma interativa — não fica no histórico do shell
gh secret set SERVIDOR_HOST

# Define o valor direto no comando
gh secret set SERVIDOR_USUARIO --body "deploy"

# Lê o valor de um arquivo — a forma correta para chaves privadas,
# que são multilinha e não sobrevivem a um copiar e colar
gh secret set CHAVE_SSH_PRIVADA < ~/.ssh/id_ed25519

# Grava em outro repositório, sem sair do diretório atual
gh secret set TOKEN_API --repo usuario/outro-projeto

Para conferir e remover:

gh secret list          # nomes e data de atualização — nunca os valores
gh secret delete TOKEN_API

Repare no que o gh secret list devolve: apenas nome e data. Uma vez gravado, o valor não pode ser lido de volta — nem pela interface, nem pela API, nem pelo CLI. Se você esqueceu qual era, não existe consulta: o caminho é substituí-lo por um novo.

Disparo Manual e Agendado

Além de eventos de push e pull request, workflows podem ser disparados manualmente ou em horários programados:

on:
  # Disparo manual pela interface do GitHub
  workflow_dispatch:
    inputs:
      ambiente:
        description: 'Ambiente de destino'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - producao

  # Agendamento via cron — roda todo dia às 2h da manhã
  schedule:
    - cron: '0 2 * * *'

O workflow_dispatch é especialmente útil para deploys controlados — o responsável escolhe o ambiente e confirma o disparo manualmente pela interface do GitHub.

Um Pipeline Completo e Realista

Reunindo tudo em um pipeline que faz sentido para um projeto real:

name: Pipeline Completo

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  qualidade:
    name: Qualidade de Código
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm test -- --coverage

  build:
    name: Build da Aplicação
    runs-on: ubuntu-latest
    needs: qualidade
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - name: Salva artefato do build
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/

  deploy-staging:
    name: Deploy em Staging
    runs-on: ubuntu-latest
    needs: build
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Baixa artefato do build
        uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
      - name: Deploy
        env:
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
        run: echo "Fazendo deploy em staging..."

O campo if garante que o job de deploy só rode quando o evento for um push direto na main — não em Pull Requests.

Referências para Aprofundamento

Documentação oficial

  • GitHub Actions Documentation — Documentação completa do GitHub Actions, incluindo referência de sintaxe, eventos disponíveis e guias por linguagem.
  • GitHub Actions Marketplace — Catálogo de actions disponíveis. Vale explorar antes de escrever qualquer automação do zero.

Leitura técnica

Prática

Exercícios

Exercício 1

Defina workflow, event, job e step. Qual desses roda em ambiente isolado, e que consequência prática isso traz?

Ver resposta

✓ Resposta: O workflow é o arquivo YAML de automação, guardado em .github/workflows/. O event é o gatilho que o dispara — um push, um pull request, um agendamento ou um disparo manual. O job é a unidade de trabalho, e é ele que roda em uma máquina virtual isolada. O step é cada passo dentro do job, seja um comando shell ou uma action pronta. A consequência do isolamento é direta: o que um job instala, baixa ou constrói não existe para os outros. Cada job começa do zero, e é por isso que compartilhar resultado entre jobs exige artefatos ou cache explícitos.

Exercício 2

Por que praticamente todo job começa com uses: actions/checkout@v4? O que acontece se esse step for esquecido?

Ver resposta

✓ Resposta: Porque o runner é uma máquina virtual limpa: ela traz o sistema operacional e ferramentas comuns, mas não traz o seu repositório. O actions/checkout clona o código no runner, já na referência que disparou o evento. Sem esse step, o ls -la do exemplo mostraria um diretório praticamente vazio, e qualquer npm ci, npm test ou comando sobre arquivos do projeto falharia com "arquivo não encontrado" — um erro que confunde justamente por parecer problema de código, quando é ausência do código.

Exercício 3

No workflow de build e testes, o que roda em paralelo e o que espera? Qual o efeito do needs sobre o tempo total e sobre o desperdício de execução?

Ver resposta

✓ Resposta: lint e testes-unitarios não declaram dependência, então partem juntos assim que o workflow dispara. testes-integracao declara needs: testes-unitarios e só começa quando esse terminar com sucesso — se os unitários falharem, o de integração sequer é executado. O efeito no tempo é que o total passa a ser o do caminho mais longo, não a soma de todos os jobs. E o efeito no desperdício é igualmente importante: testes de integração costumam ser os mais lentos e caros, e não faz sentido pagá-los quando o teste unitário já reprovou a mudança.

Exercício 4

Quantas vezes o job abaixo será executado, e o que ${{ matrix.node-version }} vale em cada uma? Que tipo de problema essa estratégia revela?

strategy:
  matrix:
    node-version: [18.x, 20.x]
Ver resposta

✓ Resposta: Duas vezes, em paralelo — uma execução com 18.x e outra com 20.x, e a variável assume o valor correspondente em cada uma. O tipo de problema que isso revela é a incompatibilidade entre versões: uma função removida, um comportamento de API que mudou, uma dependência que só compila em uma delas. Sem a matriz, o projeto é validado apenas na versão que o desenvolvedor tem instalada, e a quebra aparece no ambiente de quem usa outra — geralmente em produção, geralmente no pior horário.

Exercício 5

Por que uma credencial nunca deve ser escrita direto no arquivo de workflow? O que o GitHub faz pelos Secrets — e por que isso ainda não dispensa cuidado no run?

Ver resposta

✓ Resposta: Porque o workflow é versionado junto com o código. Escrever a credencial ali a grava no histórico do Git, onde ela continua acessível mesmo depois de removida do arquivo — remover não basta, a chave precisa ser rotacionada. Os Secrets ficam criptografados nas configurações do repositório, são injetados apenas durante a execução e o GitHub mascara automaticamente o valor nos logs. Mas mascarar não é blindar: um step que grave o segredo em arquivo, envie a um serviço externo ou apenas o transforme — um base64, por exemplo — escapa do mascaramento. Repare que o próprio exemplo do artigo grava a chave em chave.pem, e é exatamente por isso que o chmod 600 vem logo na linha seguinte.

Exercício 6

Que casos de uso o workflow_dispatch e o agendamento por cron resolvem, que push e pull_request não conseguem resolver?

Ver resposta

✓ Resposta: push e pull_request só reagem a mudanças no repositório — eles respondem à pergunta "o código mudou?". O workflow_dispatch cobre o que depende de decisão humana sem código novo: promover uma versão já testada para produção, executar um rollback, rodar uma migração pontual. E, como aceita inputs, permite parametrizar essa decisão — o ambiente com opções do exemplo é exatamente isso. Já o agendamento cobre o que precisa acontecer por tempo, não por evento: backup noturno, varredura de segurança, renovação de certificado, relatório diário. Nenhuma dessas tarefas tem um commit associado que pudesse dispará-las.

Comentários

Mais em DevOps

RDS, ElastiCache e Estratégias de Dados na AWS
RDS, ElastiCache e Estratégias de Dados na AWS

A camada mais crítica e mais difícil de recuperar quando algo dá errado: RDS…

ECS e Lambda: Containers e Serverless na AWS
ECS e Lambda: Containers e Serverless na AWS

Os serviços gerenciados de computação da AWS e seus trade-offs: os conceitos…

Route53, CloudFront e ACM: Rede e Entrega de Conteúdo
Route53, CloudFront e ACM: Rede e Entrega de Conteúdo

A camada de entrega que fica entre o usuário e a aplicação: hosted zones e…