DevOps & CI/CD

GitHub Actions: Workflows, Jobs, Steps e Actions Reutilizáveis na Prática

12 min de leitura

GitHub Actions: Workflows, Jobs, Steps e Actions Reutilizáveis na Prática

GitHub Actions: Dominando Workflows, Jobs, Steps e Actions Reutilizáveis 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. O que é GitHub Actions? 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. Estrutura Fundamental: Workflows, Jobs e Steps Entendendo a Hierarquia Uma workflow é um arquivo YAML armazenado

<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 &quot;workflow&quot; é 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: &#039;18&#039;

  • 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 &quot;Deploying application...&quot;

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: &#039;Validate Version&#039;

description: &#039;Valida se a versão no package.json segue semântica correta&#039;

inputs:

version-path:

description: &#039;Caminho para o arquivo com a versão&#039;

required: false

default: &#039;package.json&#039;

outputs:

current-version:

description: &#039;Versão extraída&#039;

value: ${{ steps.extract.outputs.version }}

is-valid:

description: &#039;Se a versão é válida (true/false)&#039;

value: ${{ steps.validate.outputs.valid }}

runs:

using: &#039;composite&#039;

steps:

  • name: Extract version

id: extract

shell: bash

run: VERSION=$(cat ${{ inputs.version-path }} | grep &#039;&quot;version&quot;&#039; | head -1 | sed &#039;s/.&quot;version&quot;: &quot;\([^&quot;]\)&quot;.*/\1/&#039;)

echo &quot;version=$VERSION&quot; &gt;&gt; $GITHUB_OUTPUT

  • name: Validate semantic versioning

id: validate

shell: bash

run: |

VERSION=&quot;${{ steps.extract.outputs.version }}&quot;

if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then

echo &quot;valid=true&quot; &gt;&gt; $GITHUB_OUTPUT

echo &quot;✓ Versão $VERSION é válida&quot;

else

echo &quot;valid=false&quot; &gt;&gt; $GITHUB_OUTPUT

echo &quot;✗ Versão $VERSION não segue semântica semver&quot;

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: [ &#039;v*&#039; ]

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: &#039;package.json&#039;

  • name: Show results

run: |

echo &quot;Versão atual: ${{ steps.version.outputs.current-version }}&quot;

echo &quot;É válida: ${{ steps.version.outputs.is-valid }}&quot;</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: &#039;Deploy Application&#039;

description: &#039;Faz deploy da aplicação para o servidor&#039;

inputs:

environment:

description: &#039;Ambiente de deploy (staging ou production)&#039;

required: true

default: &#039;staging&#039;

outputs:

deployment-url:

description: &#039;URL da aplicação deployada&#039;

value: ${{ steps.deploy.outputs.url }}

runs:

using: &#039;composite&#039;

steps:

  • name: Deploy

id: deploy

shell: bash

env:

DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

ENV: ${{ inputs.environment }}

run: |

Valida se token existe

if [ -z &quot;$DEPLOY_TOKEN&quot; ]; then

echo &quot;Erro: DEPLOY_TOKEN não configurado&quot;

exit 1

fi

Faz deploy

echo &quot;Deploying para $ENV...&quot;

Seu script de deploy aqui

Define output

if [ &quot;$ENV&quot; = &quot;production&quot; ]; then

echo &quot;url=https://app.example.com&quot; &gt;&gt; $GITHUB_OUTPUT

else

echo &quot;url=https://staging.example.com&quot; &gt;&gt; $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: &#039;production&#039;

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: &#039;18&#039;

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: &#039;20&#039;

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 &quot;Pipeline falhou&quot;

exit 1

  • name: Report success

if: success()

run: echo &quot;Tudo passou!&quot;

  • name: Always run

if: always()

run: echo &quot;Isso sempre executa, sucesso ou falha&quot;</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: &#039;18&#039;

  • name: Cache npm dependencies

uses: actions/cache@v3

with:

path: ~/.npm

key: ${{ runner.os }}-npm-${{ hashFiles(&#039;**/package-lock.json&#039;) }}

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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção
Como Usar Shell Scripting Avançado: Processos, Sinais, Trap e Scripts Robustos em Produção

Entendendo Processos em Shell Um processo em Unix/Linux é uma instância de um...

Dominando Tekton em Kubernetes: Pipelines Cloud-Native do Zero em Projetos Reais
Dominando Tekton em Kubernetes: Pipelines Cloud-Native do Zero em Projetos Reais

O que é Tekton e por que você precisa aprender Tekton é um framework open-sou...

Como Usar Docker Fundamentos: Imagens, Containers, Volumes e Redes em Produção
Como Usar Docker Fundamentos: Imagens, Containers, Volumes e Redes em Produção

Docker Fundamentos: Compreendendo o Ecossistema de Containerização Docker é u...