DevOps & CI/CD

Dominando GitHub Actions Avançado: Matrix Builds, Environments e OIDC em Projetos Reais

17 min de leitura

Dominando GitHub Actions Avançado: Matrix Builds, Environments e OIDC em Projetos Reais

Matrix Builds: Executando Testes em Múltiplas Configurações A funcionalidade de Matrix Builds é um dos recursos mais poderosos do GitHub Actions. Ela permite que você execute o mesmo workflow em diferentes combinações de versões, sistemas operacionais ou ambientes sem precisar duplicar todo o código. Imagine que você precisa testar sua aplicação Node.js nas versões 16, 18 e 20, e também em Ubuntu, Windows e macOS. Sem matrix, você escreveria três conjuntos de jobs praticamente idênticos. Com matrix, você escreve uma única configuração e o GitHub Actions gera automaticamente todas as 9 combinações possíveis. O conceito funciona através de variáveis de matriz que definem um espaço multidimensional de execução. Cada dimensão representa um eixo de variação — versão do Node, sistema operacional, ou até configurações de banco de dados. O workflow é executado uma vez para cada combinação dessas variáveis, e você acessa os valores dentro do job usando a sintaxe . Isso economiza tempo de manutenção e garante que seu

<h2>Matrix Builds: Executando Testes em Múltiplas Configurações</h2>

<p>A funcionalidade de Matrix Builds é um dos recursos mais poderosos do GitHub Actions. Ela permite que você execute o mesmo workflow em diferentes combinações de versões, sistemas operacionais ou ambientes sem precisar duplicar todo o código. Imagine que você precisa testar sua aplicação Node.js nas versões 16, 18 e 20, e também em Ubuntu, Windows e macOS. Sem matrix, você escreveria três conjuntos de jobs praticamente idênticos. Com matrix, você escreve uma única configuração e o GitHub Actions gera automaticamente todas as 9 combinações possíveis.</p>

<p>O conceito funciona através de variáveis de matriz que definem um espaço multidimensional de execução. Cada dimensão representa um eixo de variação — versão do Node, sistema operacional, ou até configurações de banco de dados. O workflow é executado uma vez para cada combinação dessas variáveis, e você acessa os valores dentro do job usando a sintaxe <code>matrix.varname</code>. Isso economiza tempo de manutenção e garante que seu código seja testado em cenários reais e diversificados.</p>

<pre><code class="language-yaml">name: Matrix Build Example

on: [push, pull_request]

jobs:

test:

runs-on: ${{ matrix.os }}

strategy:

matrix:

os: [ubuntu-latest, windows-latest, macos-latest]

node-version: [16.x, 18.x, 20.x]

steps:

  • uses: actions/checkout@v4
  • name: Setup Node.js ${{ matrix.node-version }}

uses: actions/setup-node@v4

with:

node-version: ${{ matrix.node-version }}

  • name: Install dependencies

run: npm ci

  • name: Run tests

run: npm test

  • name: Log configuration

run: echo &quot;Testing on ${{ matrix.os }} with Node ${{ matrix.node-version }}&quot;</code></pre>

<p>Neste exemplo, o job <code>test</code> será executado 9 vezes (3 sistemas operacionais × 3 versões do Node). Cada execução terá acesso aos valores específicos através de <code>matrix.os</code> e <code>matrix.node-version</code>. O GitHub Actions gerencia toda a orquestração; você apenas define as dimensões e referencia os valores.</p>

<h3>Incluindo e Excluindo Configurações</h3>

<p>Nem sempre você quer todas as combinações possíveis. Matrix permite refinar o comportamento com <code>include</code> para adicionar combinações específicas e <code>exclude</code> para remover aquelas que não fazem sentido. Por exemplo, talvez sua aplicação não funcione em Windows com Python 3.7, ou você queira adicionar um teste especial apenas para uma combinação rara.</p>

<pre><code class="language-yaml">jobs:

build:

runs-on: ${{ matrix.os }}

strategy:

matrix:

os: [ubuntu-latest, windows-latest]

python-version: [&#039;3.8&#039;, &#039;3.9&#039;, &#039;3.10&#039;]

exclude:

Python 3.8 não é testado em Windows

  • os: windows-latest

python-version: &#039;3.8&#039;

include:

Adiciona um teste especial apenas em Ubuntu com Python 3.10

  • os: ubuntu-latest

python-version: &#039;3.10&#039;

extra-flags: &#039;--strict&#039;

steps:

  • uses: actions/checkout@v4
  • name: Setup Python ${{ matrix.python-version }}

uses: actions/setup-python@v4

with:

python-version: ${{ matrix.python-version }}

  • name: Install dependencies

run: pip install -r requirements.txt

  • name: Run tests

run: pytest ${{ matrix.extra-flags }}</code></pre>

<p>Neste caso, você começa com 6 combinações (2 SOs × 3 versões), remove 1 com <code>exclude</code> (ficando com 5), e adiciona 1 novamente com <code>include</code> que contém um campo extra (<code>extra-flags</code>). Essa flexibilidade permite otimizar seus testes sem perder cobertura.</p>

<h3>Limitando Execuções Paralelas com max-parallel</h3>

<p>Por padrão, o GitHub Actions executa todos os jobs da matriz em paralelo, dentro dos limites da sua conta. Se você precisa controlar isso — seja para economizar créditos ou evitar sobrecarregar um serviço externo — use a chave <code>max-parallel</code>.</p>

<pre><code class="language-yaml">strategy:

max-parallel: 2

matrix:

os: [ubuntu-latest, windows-latest, macos-latest]

node-version: [16.x, 18.x, 20.x]</code></pre>

<p>Com <code>max-parallel: 2</code>, apenas 2 jobs rodão simultaneamente, e os demais esperam na fila. As 9 combinações serão executadas sequencialmente em lotes de 2, aumentando o tempo total mas controlando recursos.</p>

<p>---</p>

<h2>Environments: Isolamento e Controle de Acesso</h2>

<p>Environments (ambientes) no GitHub Actions permitem que você defina regras de proteção, segredos específicos e reviewers obrigatórios para diferentes fases do deployment. Pense em um ambiente como uma &quot;porta de entrada&quot; para ações críticas — produção, staging, homologação — onde você pode impor controles antes de permitir que o workflow prossiga. Um reviewer humano pode ser obrigado a aprovar um deploy para produção, ou segredos específicos podem ser acessados apenas quando certos ramos fazem deploy.</p>

<p>Cada ambiente é configurado no repositório (Settings → Environments) e referenciado no workflow através da chave <code>environment</code> no job. Isso oferece isolamento natural: segredos de produção não vazam para staging, e você tem controle granular sobre quem pode fazer o quê em cada fase.</p>

<pre><code class="language-yaml">name: Deploy Pipeline

on:

push:

branches: [main, develop]

jobs:

build:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • name: Build artifact

run: npm run build

  • name: Upload artifact

uses: actions/upload-artifact@v3

with:

name: dist

path: dist/

deploy-staging:

needs: build

runs-on: ubuntu-latest

environment: staging

steps:

  • uses: actions/download-artifact@v3

with:

name: dist

  • name: Deploy to staging

env:

DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}

API_URL: ${{ secrets.STAGING_API_URL }}

run: |

chmod 600 ~/.ssh/deploy_key

ssh -i ~/.ssh/deploy_key deploy@staging.example.com &quot;cd /app &amp;&amp; ./deploy.sh&quot;

deploy-production:

needs: build

runs-on: ubuntu-latest

environment:

name: production

url: https://example.com

steps:

  • uses: actions/download-artifact@v3

with:

name: dist

  • name: Deploy to production

env:

DEPLOY_KEY: ${{ secrets.PROD_DEPLOY_KEY }}

API_URL: ${{ secrets.PROD_API_URL }}

run: |

chmod 600 ~/.ssh/deploy_key

ssh -i ~/.ssh/deploy_key deploy@prod.example.com &quot;cd /app &amp;&amp; ./deploy.sh&quot;</code></pre>

<p>Neste exemplo, você tem dois ambientes: <code>staging</code> e <code>production</code>. Cada um pode ter seus próprios segredos (<code>STAGING_DEPLOY_KEY</code> vs <code>PROD_DEPLOY_KEY</code>). No painel do GitHub, você pode configurar que o ambiente <code>production</code> exija aprovação manual, ou que só funcione quando triggered pela branch <code>main</code>. O job <code>deploy-production</code> não começará até que essas condições sejam atendidas.</p>

<h3>Proteções e Rules de Ambiente</h3>

<p>As proteções são configuradas via interface do GitHub, mas têm impacto direto no workflow. As principais são:</p>

<ul>

<li><strong>Required reviewers</strong>: Uma ou mais pessoas devem aprovar antes do job executar.</li>

<li><strong>Deployment branches</strong>: Apenas branches específicas (geralmente <code>main</code> ou tags) podem fazer deploy neste ambiente.</li>

<li><strong>Secrets</strong>: Cada ambiente tem seu próprio conjunto de segredos, inacessíveis a outros ambientes.</li>

</ul>

<p>Essas regras garantem que mesmo que alguém abra um PR malicioso, não conseguirá fazer deploy em produção sem aprovação. O workflow continua funcionando; apenas o acesso aos segredos e a permissão para prosseguir são controlados.</p>

<pre><code class="language-yaml">deploy-production:

needs: build

runs-on: ubuntu-latest

environment:

name: production

url: https://example.com

if: github.ref == &#039;refs/heads/main&#039;

steps:

  • name: Deploy

run: echo &quot;Deploying to production...&quot;</code></pre>

<p>Aqui você combina a proteção de ambiente do GitHub com uma condição no workflow (<code>if: github.ref == &#039;refs/heads/main&#039;</code>), criando camadas de proteção. Mesmo que alguém tente fazer deploy de outra branch, o workflow não prosseguirá.</p>

<p>---</p>

<h2>OIDC: Autenticação sem Segredos Armazenados</h2>

<p>O OpenID Connect (OIDC) é uma mudança fundamental na forma como GitHub Actions autentica com provedores externos (AWS, Azure, Google Cloud, etc.). Tradicionalmente, você armazenava uma chave de acesso ou token como um segredo no repositório. Isso funciona, mas cria um ponto de falha: se o segredo vazar, qualquer pessoa consegue acessar sua infraestrutura. OIDC inverte isso: GitHub prova sua identidade dinamicamente, sem armazenar credenciais permanentes.</p>

<p>O fluxo funciona assim: seu workflow solicita um token OIDC assinado do GitHub. Você configura o provedor de nuvem (AWS, etc.) para confiar em GitHub como um provedor de identidade. Quando o token chega, o provedor verifica a assinatura e confirma que é realmente GitHub executando seu workflow. Se tudo está correto, a nuvem emite credenciais temporárias de curta duração exclusivas para aquela execução. Após o workflow terminar, essas credenciais expiram automaticamente.</p>

<pre><code class="language-yaml">name: Deploy with OIDC to AWS

on:

push:

branches: [main]

permissions:

id-token: write

contents: read

jobs:

deploy:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • name: Configure AWS credentials with OIDC

uses: aws-actions/configure-aws-credentials@v2

with:

role-to-assume: arn:aws:iam::123456789012:role/github-actions-role

aws-region: us-east-1

  • name: Upload to S3

run: |

aws s3 cp dist/ s3://my-bucket/app/ --recursive

  • name: Invalidate CloudFront

run: |

aws cloudfront create-invalidation \

--distribution-id E1234ABCD \

--paths &quot;/*&quot;</code></pre>

<p>Esse workflow não contém nenhuma chave secreta. A ação <code>aws-actions/configure-aws-credentials@v2</code> faz o trabalho pesado: solicita o token OIDC do GitHub, troca por credenciais AWS temporárias, e as injeta como variáveis de ambiente. O resto do workflow usa AWS CLI normalmente. Quando termina, as credenciais expiram.</p>

<h3>Configurando OIDC no AWS IAM</h3>

<p>Para que isso funcione, você precisa configurar uma relação de confiança no AWS. Crie uma role no IAM que permita que tokens OIDC do GitHub a assumam. Aqui está o JSON de confiança:</p>

<pre><code class="language-json">{

&quot;Version&quot;: &quot;2012-10-17&quot;,

&quot;Statement&quot;: [

{

&quot;Effect&quot;: &quot;Allow&quot;,

&quot;Principal&quot;: {

&quot;Federated&quot;: &quot;arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com&quot;

},

&quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,

&quot;Condition&quot;: {

&quot;StringEquals&quot;: {

&quot;token.actions.githubusercontent.com:aud&quot;: &quot;sts.amazonaws.com&quot;

},

&quot;StringLike&quot;: {

&quot;token.actions.githubusercontent.com:sub&quot;: &quot;repo:seu-usuario/seu-repo:ref:refs/heads/main&quot;

}

}

}

]

}</code></pre>

<p>A condição <code>StringLike</code> restringe quais workflows podem assumir essa role. Neste caso, apenas a branch <code>main</code> do seu repositório. Você pode ser tão restritivo quanto necessário: por repositório, branch, ou até por ambiente.</p>

<h3>Benefícios e Boas Práticas</h3>

<p>O OIDC oferece vários benefícios sobre segredos tradicionais:</p>

<ul>

<li><strong>Sem vazamento permanente</strong>: Credenciais expiram automaticamente. Se um atacante conseguir capturá-las, só funcionam por minutos.</li>

<li><strong>Auditoria melhorada</strong>: Cada ação é ligada a um workflow específico, branch e commit. Você sabe exatamente qual execução fez o quê.</li>

<li><strong>Escalabilidade</strong>: Você não precisa gerenciar múltiplos segredos para múltiplos ambientes. A confiança é configurada uma vez no provedor.</li>

<li><strong>Conformidade</strong>: Muitos padrões de conformidade (SOC 2, ISO 27001) preferem OIDC a credenciais estáticas.</li>

</ul>

<pre><code class="language-yaml">name: Multi-Cloud Deploy with OIDC

on:

push:

branches: [main]

permissions:

id-token: write

contents: read

jobs:

deploy-aws:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: aws-actions/configure-aws-credentials@v2

with:

role-to-assume: arn:aws:iam::111111111111:role/github-actions

aws-region: us-east-1

  • run: aws s3 cp dist/ s3://bucket-aws/ --recursive

deploy-gcp:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: google-github-actions/auth@v1

with:

workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/github

service_account: github-actions@project-id.iam.gserviceaccount.com

  • uses: google-github-actions/setup-gcloud@v1
  • run: gsutil -m cp -r dist/ gs://bucket-gcp/

deploy-azure:

runs-on: ubuntu-latest

steps:

  • uses: actions/checkout@v4
  • uses: azure/login@v1

with:

client-id: ${{ secrets.AZURE_CLIENT_ID }}

tenant-id: ${{ secrets.AZURE_TENANT_ID }}

subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

  • run: az storage blob upload-batch -s dist/ -d app --account-name myaccount</code></pre>

<p>Aqui você faz deploy para AWS, GCP e Azure no mesmo workflow, cada um usando OIDC (ou sua variante equivalente). Nenhuma chave secreta permanente está armazenada. Cada provedor valida o token OIDC e emite credenciais temporárias.</p>

<p>---</p>

<h2>Combinando Matrix, Environments e OIDC: Estratégia Completa</h2>

<p>Os três recursos funcionam muito bem juntos. Você pode usar matrix para testar múltiplas configurações, depois fazer deploy automático apenas para as que passarem, usando environments com OIDC para autenticação segura. Aqui está um exemplo realista de um pipeline CI/CD completo:</p>

<pre><code class="language-yaml">name: Complete CI/CD Pipeline

on:

push:

branches: [main, develop]

pull_request:

branches: [main, develop]

permissions:

id-token: write

contents: read

jobs:

test:

runs-on: ${{ matrix.os }}

strategy:

matrix:

os: [ubuntu-latest, windows-latest]

node-version: [18.x, 20.x]

steps:

  • uses: actions/checkout@v4
  • name: Setup Node ${{ matrix.node-version }}

uses: actions/setup-node@v4

with:

node-version: ${{ matrix.node-version }}

  • name: Cache dependencies

uses: actions/cache@v3

with:

path: node_modules

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

  • name: Install dependencies

run: npm ci

  • name: Lint

run: npm run lint

  • name: Run tests

run: npm test

  • name: Build

run: npm run build

security-scan:

runs-on: ubuntu-latest

if: github.event_name == &#039;push&#039;

steps:

  • uses: actions/checkout@v4
  • name: Run Snyk scan

uses: snyk/actions/node@master

env:

SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

deploy-staging:

needs: [test, security-scan]

if: github.ref == &#039;refs/heads/develop&#039;

runs-on: ubuntu-latest

environment: staging

steps:

  • uses: actions/checkout@v4
  • name: Setup Node

uses: actions/setup-node@v4

with:

node-version: 20.x

  • name: Install and build

run: |

npm ci

npm run build

  • name: Configure AWS with OIDC

uses: aws-actions/configure-aws-credentials@v2

with:

role-to-assume: arn:aws:iam::123456789012:role/github-staging

aws-region: us-east-1

  • name: Deploy to staging

run: |

aws s3 sync dist/ s3://staging-bucket/

aws cloudfront create-invalidation --distribution-id E123 --paths &quot;/*&quot;

deploy-production:

needs: [test, security-scan]

if: github.ref == &#039;refs/heads/main&#039; &amp;&amp; github.event_name == &#039;push&#039;

runs-on: ubuntu-latest

environment:

name: production

url: https://example.com

steps:

  • uses: actions/checkout@v4
  • name: Setup Node

uses: actions/setup-node@v4

with:

node-version: 20.x

  • name: Install and build

run: |

npm ci

npm run build

  • name: Configure AWS with OIDC

uses: aws-actions/configure-aws-credentials@v2

with:

role-to-assume: arn:aws:iam::123456789012:role/github-production

aws-region: us-east-1

  • name: Deploy to production

run: |

aws s3 sync dist/ s3://prod-bucket/ --delete

aws cloudfront create-invalidation --distribution-id E456 --paths &quot;/*&quot;

  • name: Create deployment record

run: |

aws dynamodb put-item \

--table-name deployments \

--item &quot;timestamp={S=$(date -u +%s)},repo={S=seu-repo},commit={S=$GITHUB_SHA}&quot;</code></pre>

<p>Este pipeline:</p>

<ol>

<li><strong>Testa</strong> em múltiplas configurações (matriz com 2 SOs × 2 versões Node = 4 jobs em paralelo)</li>

<li><strong>Escaneia segurança</strong> apenas em merges (não em PRs)</li>

<li><strong>Faz deploy em staging</strong> automaticamente quando código chega em <code>develop</code>, usando environment com OIDC</li>

<li><strong>Faz deploy em production</strong> apenas após aprová-lo (environment com reviewers obrigatórios), quando merged para <code>main</code></li>

<li><strong>Registra</strong> cada deployment para auditoria</li>

</ol>

<p>Tudo sem uma única chave secreta permanente armazenada no repositório.</p>

<p>---</p>

<h2>Conclusão</h2>

<p>Dominando esses três pilares — <strong>Matrix Builds</strong>, <strong>Environments</strong> e <strong>OIDC</strong> — você constrói pipelines robustos, escaláveis e seguros. Matrix elimina duplicação e garante cobertura em múltiplas plataformas. Environments isolam diferentes fases de entrega e impõem controle de acesso humano quando crítico. OIDC substitui segredos permanentes por credenciais temporárias verificadas criptograficamente, reduzindo drasticamente o risco de exposição.</p>

<p>Na prática, isso significa que você consegue testar seu código em Windows, macOS e Linux, com múltiplas versões de runtime, tudo automaticamente. Seu staging recebe updates contínuos, mas produção só avança com aprovação. E em nenhum momento há chaves de API ou senhas armazenadas como texto no repositório — apenas tokens de curta duração gerados sob demanda. Isso é infraestrutura moderna.</p>

<p>---</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs" target="_blank" rel="noopener noreferrer">GitHub Actions Documentation - Matrix Builds</a></li>

<li><a href="https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment" target="_blank" rel="noopener noreferrer">GitHub Actions Documentation - Environments</a></li>

<li><a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect" target="_blank" rel="noopener noreferrer">GitHub Actions Documentation - OIDC</a></li>

<li><a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services" target="_blank" rel="noopener noreferrer">AWS - Configuring OpenID Connect in Amazon Web Services</a></li>

<li><a href="https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions" target="_blank" rel="noopener noreferrer">Best Practices for GitHub Actions Security</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em DevOps & CI/CD

O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State
O que Todo Dev Deve Saber sobre Terraform Avançado: Módulos, Workspaces e Remote State

Entendendo Módulos no Terraform Um módulo no Terraform é um diretório contend...

O que Todo Dev Deve Saber sobre AWS Avançado: EKS, ECS, Lambda e RDS em Ambientes Reais
O que Todo Dev Deve Saber sobre AWS Avançado: EKS, ECS, Lambda e RDS em Ambientes Reais

Arquitetura Moderna na AWS: Da Orquestração de Contêineres ao Serverless A co...

Dominando Linux para DevOps: Filesystem, Permissões e Navegação no Terminal em Projetos Reais
Dominando Linux para DevOps: Filesystem, Permissões e Navegação no Terminal em Projetos Reais

Entendendo o Filesystem Linux O filesystem Linux é a estrutura fundamental qu...