GitHub Actions básico: workflow, job e step

1. Visão geral

GitHub Actions é uma ferramenta CI/CD integrada nativamente ao GitHub, que ajuda você a automatizar build, test e deploy sem precisar de um servidor CI separado. Este artigo explica três conceitos fundamentais: workflow, job e step, com exemplos práticos para um projeto Spring Boot usando Maven.

Depois de ler, você saberá escrever um arquivo de workflow que execute testes automaticamente cada vez que você faz push do código, entender como os jobs rodam em paralelo ou em sequência, e saber como fazer debug quando o workflow falha.

2. O que é um Workflow?

Um workflow é um arquivo YAML localizado na pasta .github/workflows/ do seu repositório. Cada workflow define quando executar (trigger) e o que fazer (jobs).

# .github/workflows/ci.yml
name: CI Pipeline

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

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 21
        uses: actions/setup-java@v4
        with:
          java-version: '21'
          distribution: 'temurin'
      - name: Build with Maven
        run: mvn -B package

Giải thích:

  • name no início do arquivo é o nome exibido na aba Actions do GitHub.
  • on declara o trigger: aqui o workflow roda quando há push ou pull_request para a branch main.
  • jobs.build é um job único chamado build, que roda em uma máquina virtual ubuntu-latest.

Atenção: O arquivo deve estar no caminho correto .github/workflows/*.yml; se estiver na pasta errada, o GitHub não reconhecerá o workflow.

3. Job: unidade de execução independente

Um workflow pode ter vários jobs. Cada job roda em um runner (máquina virtual) separado, e por padrão os jobs rodam em paralelo a menos que você declare needs.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B test

  deploy:
    runs-on: ubuntu-latest
    needs: test
    steps:
      - run: echo "Deploying application..."

Giải thích:

  • test e deploy são dois jobs distintos; cada job tem seu próprio ambiente de runner limpo e não compartilha arquivos do sistema com o outro.
  • needs: test força deploy a esperar que test seja bem-sucedido antes de rodar — criando uma relação sequencial.
  • Se não houver needs, o GitHub Actions rodará test e deploy simultaneamente.

3.1. Compartilhando dados entre jobs

Como cada job tem seu próprio runner, para passar arquivos de um job para outro você precisa usar artifacts.

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: mvn -B package
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: target/*.jar

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-jar

Giải thích:

  • upload-artifact salva o arquivo .jar do job build no armazenamento temporário do GitHub.
  • download-artifact no job deploy recupera esse arquivo para o novo runner.
  • Esta é a única maneira de passar dados entre jobs (você não pode usar variáveis de ambiente comuns).

4. Step: a menor unidade de operação

Um step é uma ação específica dentro de um job, executado sequencialmente de cima para baixo. Existem dois tipos principais de steps: usar uses (chamar uma action pronta) ou usar run (executar um comando shell).

steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Cache Maven dependencies
    uses: actions/cache@v4
    with:
      path: ~/.m2/repository
      key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}

  - name: Run tests
    run: mvn -B test

  - name: Print result
    run: echo "Test step finished with status ${{ job.status }}"

Giải thích:

  • Step Checkout code usa a action pronta actions/checkout para trazer o código-fonte para o runner.
  • Step Cache Maven dependencies faz cache do diretório ~/.m2/repository, ajudando builds futuros a serem mais rápidos porque não precisam baixar as dependências novamente.
  • key do cache é baseado no hash do pom.xml — quando pom.xml muda, o cache antigo é descartado e um novo é criado.
  • O último step usa run para executar um comando shell comum, podendo referenciar contextos como ${{ job.status }}.

5. Variáveis de ambiente e secrets

Frequentemente você precisa passar configurações sensíveis (chaves de API, tokens) para o workflow sem fazer hardcode no arquivo YAML.

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      SPRING_PROFILES_ACTIVE: prod
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to server
        env:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with profile $SPRING_PROFILES_ACTIVE"
          curl -H "Authorization: Bearer $DEPLOY_TOKEN" https://api.example.com/deploy

Giải thích:

  • env no nível do job se aplica a todos os steps desse job (SPRING_PROFILES_ACTIVE).
  • env no nível do step se aplica apenas a esse step (DEPLOY_TOKEN).
  • secrets.DEPLOY_TOKEN é obtido de Settings → Secrets and variables → Actions do seu repositório e nunca mostra o valor real nos logs.

Atenção: Nunca imprima diretamente o valor do secret com echo $DEPLOY_TOKEN nos logs — o GitHub automaticamente fará o mask, mas o melhor é evitar imprimir secrets.

6. Matrix: rodando múltiplas versões simultaneamente

Quando você precisa testar em múltiplas versões de Java ou de SO, matrix ajuda você a não ter que escrever jobs repetidos.

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        java: [17, 21]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: ${{ matrix.java }}
          distribution: 'temurin'
      - run: mvn -B test

Giải thích:

  • strategy.matrix.java declara uma lista de valores [17, 21].
  • O GitHub Actions automaticamente cria 2 jobs em paralelo, cada job rodando com uma versão de Java correspondente.
  • Esta abordagem economiza esforço ao escrever YAML repetitivo, sendo ideal quando você precisa garantir que o código roda bem em múltiplos ambientes.

7. Testando o workflow com testes simples

O workflow não testa diretamente com JUnit, mas você deve garantir que o passo mvn test no workflow execute corretamente a suite de testes do seu projeto. Por exemplo, um teste simples para OrderService que a CI executará:

class OrderServiceTest {

    private final OrderService orderService = new OrderService();

    @Test
    void shouldCalculateTotalPriceCorrectly() {
        Order order = new Order("ORD-001", List.of(
                new OrderItem("Keyboard", 2, BigDecimal.valueOf(50)),
                new OrderItem("Mouse", 1, BigDecimal.valueOf(20))
        ));

        BigDecimal total = orderService.calculateTotal(order);

        assertThat(total).isEqualByComparingTo(BigDecimal.valueOf(120));
    }
}

Giải thích:

  • Este teste está em src/test/java e será executado por mvn -B test no step do workflow automaticamente.
  • Se o teste falhar, o step Run tests reporta um erro (exit code diferente de 0), o job para e jobs dependentes (needs) não continuam.
  • Este é o motivo pelo qual CI é útil: código com erro é bloqueado antes de fazer merge para main.

Resultado ao rodar no GitHub Actions UI (aba Actions):

✓ Checkout code
✓ Set up JDK 21
✓ Build with Maven
✓ Run tests (shouldCalculateTotalPriceCorrectly PASSED)

8. Erros comuns

  • Indentação YAML incorreta: GitHub Actions usa YAML e uma indentação incorreta de um espaço faz com que o job ou step não seja reconhecido, geralmente reportando erro “Invalid workflow file”.
  • Esqueceu de runs-on: cada job deve ter obrigatoriamente runs-on; sua falta fará o workflow falhar durante o parsing.
  • Usar variáveis de ambiente de um job em outro: env e variáveis de output normalmente não são automaticamente passadas entre jobs; você precisa usar outputs ou artifact.
  • Cache key não muda quando dependencies mudam: se o key do cache for muito genérico (por exemplo, apenas maven-cache), você pode usar acidentalmente um cache antigo contendo dependências desatualizadas.
  • Rodar workflow pesado em todo push: se você não limitar paths ou branches, até pequenos commits (corrigir README) dispararão todo o pipeline de build + test, desperdiçando minutos de execução do Actions (há limite de uso gratuito).

Atenção: Se seu projeto precisa apenas de um build simples e não tem muitos ambientes, não é necessário usar matrix ou artifact — adicionar complexidade desnecessária deixará o workflow difícil de manter.

9. Conclusão

Um workflow é um arquivo YAML que define todo o pipeline; um job é um bloco de execução independente que roda em seu próprio runner; e um step é cada ação específica dentro de um job. Entender bem esses três níveis ajuda você a organizar seu pipeline CI/CD claramente: qual job roda em paralelo, qual deve esperar o outro via needs, e quando usar artifact para compartilhar dados.

Quando está começando, você deve manter o workflow simples (um job, alguns steps básicos: checkout, setup Java, test). Conforme seu projeto crescer, adicione matrix, cache, e separe build/test/deploy em jobs distintos para facilitar o gerenciamento e debug.

github-actionsci-cddevopsjavaautomation