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:
- Sem detecção de mudanças: Push em
packages/utilsrebuilda tudo - Sem paralelização: Testes rodam sequencialmente
- 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
| Runner | Custo 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:
- Máquina reiniciou mas o serviço não subiu
- Token expirou (roda a cada 30 dias)
- 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:
- Reduzir tempo de build em 80%+ usando detecção de mudanças, cache e paralelização
- Cortar custos em 50%+ com seleção inteligente de runners e controle de concorrência
- Escalar com confiança com runners próprios e ARC
- 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 🚀
Explore ferramentas relacionadas
Experimente estas ferramentas gratuitas do Pockit