Back

GitHub Actions 2026: Guia completo de CI/CD para monorepos e Self-Hosted Runners

Seu workflow do GitHub Actions demora 45 minutos. O time tá frustrado. Todo push dispara um rebuild completo do monorepo inteiro. E seus minutos mensais acabam mais rápido do que você consegue dizer "alerta de cobrança".

Familiar? Você não tá sozinho. Conforme os codebases crescem e monorepos viram o padrão, os pipelines de CI/CD que funcionavam bem pra um pacote só viram gargalo do nada. Mas o GitHub Actions evoluiu bastante, e a maioria dos devs não tá usando todo o potencial dele.

Esse guia cobre tudo que você precisa saber sobre GitHub Actions em 2026: de otimizar workflows de monorepos até configurar runners próprios, de estratégias avançadas de cache até gestão de custos. Vamos transformar seu CI/CD de gargalo em vantagem competitiva.

O desafio do monorepo: Por que seus builds são lentos

Monorepos tão em todo lugar agora. Turborepo, Nx, Lerna, Rush—as ferramentas amadureceram. Mas o CI/CD não acompanhou pra maioria dos times.

O problema

# O jeito ingênuo: builda tudo em todo push name: CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run build - run: npm test

Esse workflow tem três problemas críticos:

  1. Sem detecção de mudanças: Push em packages/utils rebuilda tudo
  2. Sem paralelização: Testes rodam sequencialmente
  3. Sem cache: Toda execução começa do zero

Vamos arrumar os três.

Detecção de mudanças: Só builda o que mudou

O insight chave: num monorepo, a maioria dos commits só afeta um subconjunto de pacotes. A gente deveria só buildar e testar o que realmente mudou.

Usando paths-filter

name: CI on: push: branches: [main] pull_request: jobs: changes: runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | frontend: - 'packages/frontend/**' - 'packages/shared/**' backend: - 'packages/backend/**' - 'packages/shared/**' frontend: needs: changes if: ${{ needs.changes.outputs.frontend == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: npm ci - run: npm run build --workspace=packages/frontend - run: npm test --workspace=packages/frontend

Resultado: Se você só muda packages/frontend/src/Button.tsx, só o job de frontend roda. Backend é pulado completamente.

Usando a detecção nativa do Turborepo

Se você usa Turborepo, ele tem detecção de mudanças embutida:

jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Necessário pra detecção de mudanças - uses: pnpm/action-setup@v3 - uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - run: pnpm install - run: pnpm turbo build --filter='...[origin/main]'

O --filter='...[origin/main]' diz pro Turborepo rodar tasks só pros pacotes que mudaram desde origin/main.

Cache avançado: Além do básico

Cache é onde a maioria dos times deixa performance na mesa.

Camada 1: Cache do gerenciador de pacotes

- uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm'

Camada 2: Cache de build com Turborepo

O remote caching do Turborepo é um divisor de águas:

- run: pnpm turbo build --filter='...[origin/main]' env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }}

Com remote caching habilitado, se um colega já buildou packages/utils com os mesmos inputs, você pega cache hit—mesmo numa máquina CI limpa.

Camada 3: Cache personalizado pra dependências pesadas

- name: Cachear browsers do Playwright uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} - name: Instalar Playwright run: npx playwright install --with-deps if: steps.cache-playwright.outputs.cache-hit != 'true'

Camada 4: Docker Layer Caching

- uses: docker/build-push-action@v5 with: context: . push: true tags: myapp:latest cache-from: type=gha cache-to: type=gha,mode=max

O type=gha usa o cache do GitHub Actions pras layers do Docker. Pode cortar tempo de build do Docker em 80%+.

Matrix builds: Paraleliza tudo

Matrix builds deixam você rodar o mesmo job com configurações diferentes em paralelo.

Matrix básica

jobs: test: runs-on: ubuntu-latest strategy: matrix: node: [18, 20, 22] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm ci - run: npm test

Isso cria 9 jobs paralelos (3 versões do Node × 3 OS).

Matrix dinâmica pra monorepos

Gera sua matrix dinamicamente baseado no que mudou:

jobs: detect: runs-on: ubuntu-latest outputs: packages: ${{ steps.detect.outputs.packages }} steps: - uses: actions/checkout@v4 - id: detect run: | packages=$(ls -d packages/*/ | jq -R -s -c 'split("\n")[:-1]') echo "packages=$packages" >> $GITHUB_OUTPUT test: needs: detect runs-on: ubuntu-latest strategy: matrix: package: ${{ fromJson(needs.detect.outputs.packages) }} steps: - uses: actions/checkout@v4 - run: npm ci - run: npm test --workspace=${{ matrix.package }}

Agora cada pacote testa no seu próprio job paralelo.

Fail-Fast vs Completar tudo

Por padrão, se um job da matrix falha, todos os outros são cancelados. Às vezes você quer que todos completem:

strategy: fail-fast: false # Continua outros jobs mesmo se um falhar matrix: node: [18, 20, 22]

Self-Hosted Runners: Quando e como

Runners do GitHub são convenientes mas têm limitações:

  • 7GB RAM, 2 CPUs (padrão)
  • Sem storage persistente
  • Cobrança por minuto se acumula
  • Sem acesso a GPU

Runners próprios resolvem tudo isso.

Quando usar runners próprios

Use quando:

  • Você precisa de mais recursos (RAM, CPU, GPU)
  • Você tem jobs longos que são caros em hosted
  • Você precisa de acesso a recursos on-premise
  • Você faz workloads de ML que precisam de GPUs

Não use quando:

  • Você é um time pequeno com builds simples
  • Você não consegue manter a infra
  • Isolamento de segurança é primordial

Configurando um runner próprio

# Baixa o runner mkdir actions-runner && cd actions-runner curl -o actions-runner-linux-x64.tar.gz -L https://github.com/actions/runner/releases/download/v2.320.0/actions-runner-linux-x64.tar.gz tar xzf actions-runner-linux-x64.tar.gz # Configura ./config.sh --url https://github.com/your-org/your-repo \ --token YOUR_TOKEN \ --labels gpu,linux,x64 # Roda como serviço sudo ./svc.sh install sudo ./svc.sh start

Usa no workflow:

jobs: ml-training: runs-on: [self-hosted, gpu, linux] steps: - uses: actions/checkout@v4 - run: python train.py

Escalando com Actions Runner Controller (ARC)

Pra ambientes Kubernetes, ARC auto-escala runners baseado na demanda:

# values.yaml pro ARC controllerServiceAccount: namespace: arc-systems name: arc-controller githubConfigUrl: "https://github.com/your-org" githubConfigSecret: github-config-secret maxRunners: 10 minRunners: 1 template: spec: containers: - name: runner image: ghcr.io/actions/actions-runner:latest resources: requests: cpu: 2 memory: 4Gi

Runners sobem quando tem jobs na fila e descem quando tão idle.

Estratégias de otimização de custos

1. Use Ubuntu em vez de macOS/Windows

RunnerCusto por minuto
ubuntu-latest$0.008
windows-latest$0.016 (2x)
macos-latest$0.08 (10x)

Só use macOS pra builds de iOS ou testes específicos de macOS.

2. Cancele execuções redundantes

concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true

3. Use runners maiores estrategicamente

GitHub agora oferece runners maiores (4x, 8x, 16x). Contraintuitivamente, podem ser mais baratos:

jobs: build: runs-on: ubuntu-latest-8-cores

Se seu build demora 20 min em 2 cores mas só 6 min em 8 cores, você economiza apesar do custo por minuto maior.

4. Coloque timeout nos jobs

jobs: build: runs-on: ubuntu-latest timeout-minutes: 30

5. Agende jobs não urgentes

Roda jobs caros em horários de baixo tráfego:

on: schedule: - cron: '0 2 * * *' # 2 AM UTC diário

Padrões avançados

Workflows reutilizáveis

# .github/workflows/reusable-test.yml name: Reusable Test Workflow on: workflow_call: inputs: node-version: required: true type: string jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ inputs.node-version }} - run: npm ci - run: npm test

Usa de outro workflow:

jobs: call-reusable: uses: ./.github/workflows/reusable-test.yml with: node-version: '22'

Composite Actions

Agrupa múltiplos steps numa ação reutilizável:

# .github/actions/setup-project/action.yml name: 'Setup Project' description: 'Setup Node.js, install deps, and cache' runs: using: 'composite' steps: - uses: pnpm/action-setup@v3 with: version: 9 - uses: actions/setup-node@v4 with: node-version: 22 cache: 'pnpm' - run: pnpm install --frozen-lockfile shell: bash

Usa:

steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - run: pnpm build

Regras de proteção de ambientes

Pra deploys em produção, exige aprovações:

jobs: deploy-prod: runs-on: ubuntu-latest environment: name: production url: https://myapp.com steps: - run: ./deploy.sh

Configura o ambiente production nas settings do repo pra exigir reviews.

OIDC pra autenticação na cloud

Pare de guardar credenciais de longa duração. Use OIDC:

jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole aws-region: us-east-1 - run: aws s3 sync ./dist s3://my-bucket

Sem secrets guardados—GitHub gera credenciais temporárias via OIDC.

Troubleshooting: Problemas comuns

"Resource not accessible by integration"

Seu workflow não tem as permissões certas:

permissions: contents: read pull-requests: write issues: write

Cache não restaura

Confere sua cache key. Problemas comuns:

  • Lockfile não incluído no hash
  • OS do runner diferente entre salvar e restaurar
  • Limite de cache excedido (10GB por repo)
- uses: actions/cache@v4 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | npm-${{ runner.os }}-

Jobs da matrix dão timeout

Se os jobs travam, adiciona timeouts explícitos e debugging:

jobs: test: timeout-minutes: 30 steps: - run: npm test timeout-minutes: 20 env: DEBUG: '*'

Runner próprio desconecta

Causas comuns:

  1. Máquina reiniciou mas o serviço não subiu
  2. Token expirou (roda a cada 30 dias)
  3. Disco cheio de artifacts de build

Configura monitoramento:

# Confere status do runner sudo ./svc.sh status # Vê logs sudo journalctl -u actions.runner.*

O workflow completo pra monorepos

Aqui um workflow pronto pra produção que combina tudo:

name: CI/CD on: push: branches: [main] pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: changes: runs-on: ubuntu-latest outputs: packages: ${{ steps.filter.outputs.changes }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | frontend: - 'packages/frontend/**' backend: - 'packages/backend/**' shared: - 'packages/shared/**' build-and-test: needs: changes if: ${{ needs.changes.outputs.packages != '[]' }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: package: ${{ fromJson(needs.changes.outputs.packages) }} steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - name: Build run: pnpm turbo build --filter=${{ matrix.package }} env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} - name: Test run: pnpm turbo test --filter=${{ matrix.package }} - name: Upload coverage uses: codecov/codecov-action@v4 with: flags: ${{ matrix.package }} deploy: needs: build-and-test if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-east-1 - run: pnpm deploy

Conclusão: De 45 minutos pra 5 minutos

Com essas técnicas você pode:

  1. Reduzir tempo de build em 80%+ usando detecção de mudanças, cache e paralelização
  2. Cortar custos em 50%+ com seleção inteligente de runners e controle de concorrência
  3. Escalar com confiança com runners próprios e ARC
  4. Deploys seguros com OIDC e proteção de ambientes

GitHub Actions cresceu de uma simples ferramenta de CI pra uma plataforma de automação poderosa. Os times que dominam têm uma vantagem significativa em velocidade de entrega e experiência do dev.

Comece com uma otimização—talvez detecção de mudanças ou remote caching. Meça a melhoria. Depois itere. Seu eu futuro (e seu time) vão agradecer.

Agora vai fazer seus pipelines rápidos 🚀

GitHub ActionsCI/CDDevOpsMonorepoAutomationSelf-Hosted Runners

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit