<h2>GitHub Actions: Dominando Workflows, Jobs, Steps e Actions Reutilizáveis</h2>
<p>GitHub Actions é a solução nativa do GitHub para automação e integração contínua (CI/CD). Diferentemente de ferramentas externas, ela integra-se perfeitamente ao seu repositório, elimina a complexidade de configurações em servidores separados e permite que você automatize praticamente qualquer fluxo de trabalho com apenas alguns arquivos YAML. Neste artigo, vamos entender desde o conceito fundamental até a criação de actions reutilizáveis que você pode compartilhar entre projetos.</p>
<h3>O que é GitHub Actions?</h3>
<p>GitHub Actions funciona com base em eventos. Quando algo acontece no seu repositório — como um push, um pull request ou até uma publicação de release — uma "workflow" é acionada. Essa workflow contém a lógica de execução: testes, builds, deployments, notificações e muito mais. O grande diferencial é que tudo roda em máquinas virtuais fornecidas pelo GitHub, sem necessidade de infraestrutura própria.</p>
<h2>Estrutura Fundamental: Workflows, Jobs e Steps</h2>
<h3>Entendendo a Hierarquia</h3>
<p>Uma workflow é um arquivo YAML armazenado em <code>.github/workflows/</code> que define o fluxo de automação. Dentro de uma workflow, você tem <strong>jobs</strong> — unidades independentes de trabalho que podem rodar em paralelo ou sequencial. Dentro de cada job, existem <strong>steps</strong> — as ações individuais que executam comandos ou rodam ações pré-construídas.</p>
<p>A hierarquia é clara:</p>
<pre><code>Workflow (arquivo .yaml)
├── Job 1
│ ├── Step 1
│ ├── Step 2
│ └── Step 3
└── Job 2
├── Step 1
└── Step 2</code></pre>
<h3>Exemplo Prático: Workflow Básica</h3>
<p>Vamos criar uma workflow que executa testes automaticamente quando há um push na branch <code>main</code>. Crie o arquivo <code>.github/workflows/ci.yaml</code>:</p>
<pre><code class="language-yaml">name: CI Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
if: always()</code></pre>
<p>Neste exemplo, o job <code>test</code> é executado em uma máquina Ubuntu. Os steps seguem uma sequência: checkout do código, setup do Node.js, instalação de dependências, execução de lint e testes. O <code>if: always()</code> garante que a cobertura seja enviada mesmo se os testes falharem.</p>
<h3>Executando Jobs em Paralelo e Sequência</h3>
<p>Por padrão, jobs executam em paralelo. Se você precisar que um job dependa de outro, use a palavra-chave <code>needs</code>. Veja este exemplo onde o job de deploy depende do job de build:</p>
<pre><code class="language-yaml">name: Build and Deploy
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: dist/
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build
- name: Deploy to production
run: |
echo "Deploying application..."
Seu script de deploy aqui</code></pre>
<p>Aqui, o job <code>deploy</code> só começa após o sucesso do job <code>build</code> — isso é essencial para garantir que você só faça deploy de uma versão que foi testada.</p>
<h2>Actions Reutilizáveis: Criando Componentes Modulares</h2>
<h3>Por Que Actions Reutilizáveis Importam</h3>
<p>Conforme seus projetos crescem, você notará que certos passos são repetidos em múltiplas workflows. Ao invés de copiar e colar o mesmo código YAML, você pode criar uma <strong>action reutilizável</strong> — um componente que encapsula lógica e pode ser utilizado em qualquer workflow, até em repositórios diferentes.</p>
<p>Actions reutilizáveis funcionam como funções: recebem inputs, executam lógica, produzem outputs e podem ser compartilhadas.</p>
<h3>Estrutura de uma Action Reutilizável</h3>
<p>Uma action reutilizável é definida em um arquivo <code>action.yaml</code> e pode conter scripts em shell, JavaScript ou usar imagens Docker. Vamos criar uma action que valida a versão de um projeto:</p>
<pre><code class="language-yaml">name: 'Validate Version'
description: 'Valida se a versão no package.json segue semântica correta'
inputs:
version-path:
description: 'Caminho para o arquivo com a versão'
required: false
default: 'package.json'
outputs:
current-version:
description: 'Versão extraída'
value: ${{ steps.extract.outputs.version }}
is-valid:
description: 'Se a versão é válida (true/false)'
value: ${{ steps.validate.outputs.valid }}
runs:
using: 'composite'
steps:
- name: Extract version
id: extract
shell: bash
run: VERSION=$(cat ${{ inputs.version-path }} | grep '"version"' | head -1 | sed 's/."version": "\([^"]\)".*/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Validate semantic versioning
id: validate
shell: bash
run: |
VERSION="${{ steps.extract.outputs.version }}"
if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "valid=true" >> $GITHUB_OUTPUT
echo "✓ Versão $VERSION é válida"
else
echo "valid=false" >> $GITHUB_OUTPUT
echo "✗ Versão $VERSION não segue semântica semver"
exit 1
fi</code></pre>
<p>Salve este arquivo como <code>.github/actions/validate-version/action.yaml</code>. Agora você pode usar essa action em qualquer workflow:</p>
<pre><code class="language-yaml">name: Validate Release
on:
push:
tags: [ 'v*' ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate project version
id: version
uses: ./.github/actions/validate-version
with:
version-path: 'package.json'
- name: Show results
run: |
echo "Versão atual: ${{ steps.version.outputs.current-version }}"
echo "É válida: ${{ steps.version.outputs.is-valid }}"</code></pre>
<h3>Usando Secrets e Variáveis em Actions</h3>
<p>Actions reutilizáveis frequentemente precisam de dados sensíveis, como tokens ou senhas. O GitHub fornece uma forma segura de usar esses dados através de secrets. Vamos criar uma action que faz deploy usando um token:</p>
<pre><code class="language-yaml">name: 'Deploy Application'
description: 'Faz deploy da aplicação para o servidor'
inputs:
environment:
description: 'Ambiente de deploy (staging ou production)'
required: true
default: 'staging'
outputs:
deployment-url:
description: 'URL da aplicação deployada'
value: ${{ steps.deploy.outputs.url }}
runs:
using: 'composite'
steps:
- name: Deploy
id: deploy
shell: bash
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
ENV: ${{ inputs.environment }}
run: |
Valida se token existe
if [ -z "$DEPLOY_TOKEN" ]; then
echo "Erro: DEPLOY_TOKEN não configurado"
exit 1
fi
Faz deploy
echo "Deploying para $ENV..."
Seu script de deploy aqui
Define output
if [ "$ENV" = "production" ]; then
echo "url=https://app.example.com" >> $GITHUB_OUTPUT
else
echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
fi</code></pre>
<p>Use assim em sua workflow:</p>
<pre><code class="language-yaml">jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
uses: ./.github/actions/deploy
with:
environment: 'production'
secrets:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}</code></pre>
<h2>Padrões Avançados e Melhores Práticas</h2>
<h3>Reutilizando Workflows Inteiras</h3>
<p>Você também pode reutilizar workflows completas! Isso é útil quando múltiplos projetos precisam do mesmo pipeline. Crie <code>.github/workflows/reusable-test.yaml</code>:</p>
<pre><code class="language-yaml">name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
type: string
required: false
default: '18'
secrets:
npm-token:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install dependencies
run: npm ci
env:
NPM_TOKEN: ${{ secrets.npm-token }}
- name: Run tests
run: npm test</code></pre>
<p>Em outro repositório, você chama assim:</p>
<pre><code class="language-yaml">name: CI
on: [ push, pull_request ]
jobs:
test:
uses: seu-usuario/seu-repo/.github/workflows/reusable-test.yaml@main
with:
node-version: '20'
secrets:
npm-token: ${{ secrets.NPM_TOKEN }}</code></pre>
<h3>Tratamento de Erros e Condicionais</h3>
<p>GitHub Actions oferece várias funções para controlar o fluxo baseado em resultados:</p>
<pre><code class="language-yaml">jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check syntax
id: syntax
continue-on-error: true
run: |
npm run lint
- name: Notify on failure
if: failure()
run: |
echo "Pipeline falhou"
exit 1
- name: Report success
if: success()
run: echo "Tudo passou!"
- name: Always run
if: always()
run: echo "Isso sempre executa, sucesso ou falha"</code></pre>
<p>O <code>if: failure()</code> executa apenas se algum step anterior falhar. O <code>if: success()</code> executa apenas se todos forem bem-sucedidos. O <code>if: always()</code> ignora completamente o status anterior.</p>
<h3>Cacheando Dependências para Performance</h3>
<p>Um padrão essencial é cachear dependências para evitar re-downloads:</p>
<pre><code class="language-yaml">jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build</code></pre>
<p>O cache usa um hash do <code>package-lock.json</code> como chave. Se o arquivo não mudou, as dependências em cache são reutilizadas, economizando tempo e largura de banda.</p>
<h2>Conclusão</h2>
<p>GitHub Actions transforma a forma como você automatiza seu fluxo de desenvolvimento. Os três pontos principais que você deve levar adiante são:</p>
<ol>
<li><strong>Workflows, Jobs e Steps são blocos de construção hierárquicos</strong>: comece simples, entenda como eles se relacionam, e escale conforme necessário. Use <code>needs</code> para criar dependências entre jobs quando o pipeline exigir sequência.</li>
</ol>
<ol>
<li><strong>Actions reutilizáveis reduzem duplicação e aumentam manutenibilidade</strong>: encapsule lógica comum em actions e compartilhe-as. Isso não apenas economiza linhas de código, mas torna sua pipeline mais profissional e fácil de manter.</li>
</ol>
<ol>
<li><strong>Performance e segurança são responsabilidades suas</strong>: implemente caching estrategicamente, nunca hardcode secrets (use a gestão de secrets nativa), e sempre teste suas workflows antes de mergear em produção.</li>
</ol>
<h2>Referências</h2>
<ul>
<li><a href="https://docs.github.com/en/actions" target="_blank" rel="noopener noreferrer">Documentação Oficial GitHub Actions</a></li>
<li><a href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action" target="_blank" rel="noopener noreferrer">Criar uma Action Reutilizável</a></li>
<li><a href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions" target="_blank" rel="noopener noreferrer">Workflow Syntax for GitHub Actions</a></li>
<li><a href="https://github.com/skills/github-actions" target="_blank" rel="noopener noreferrer">GitHub Actions: Learn by Doing</a></li>
<li><a href="https://docs.github.com/en/actions/guides" target="_blank" rel="noopener noreferrer">Best Practices for GitHub Actions</a></li>
</ul>
<p><!-- FIM --></p>