Montar SquadSolicitar Orçamento

Blog

Nossas últimas novidades
Tempo de Leitura
9
min

Guia de CI/CD no Git para times pequenos: performance, cache e runners

Aprenda a diagnosticar pipelines lentos e reduzir filas com estratégia de runners, tags/labels, cache e Docker layer caching. Inclui ARM64 vs x64 e macOS runners.
23 de fevereiro de 2026

Pipeline lento quase nunca é “só falta de CPU”.

Na prática, times pequenos sofrem com três dores recorrentes:

  • Jobs esperando em fila porque caíram no runner “errado” (ou no runner “certo”, mas lotado).
  • Build repetindo trabalho porque cache não existe, está mal configurado, ou invalida a cada commit.
  • Builds Docker sempre “do zero” porque layer cache não está sendo reaproveitado.

Este guia é um passo a passo para você atacar essas dores sem depender de “milagre” e sem criar um CI/CD frágil.

  • Se você usa GitLab CI/CD, GitHub Actions, ou ambos, este post cobre os conceitos e dá exemplos dos dois.
  • Se você tem builds em ARM64 (Graviton / Apple Silicon) e x64, aqui tem um bloco específico para evitar armadilhas de arquitetura.

Diagrama: fila vs execução em pipelines

Mapa da série CI/CD

1) Diagnóstico rápido: o gargalo é fila ou execução?

Antes de otimizar, você precisa separar duas métricas.

  • Tempo de fila (queue time): tempo entre o job ser criado e efetivamente começar.
  • Tempo de execução (run time): tempo real que o job ficou rodando.

Isso importa porque as correções são diferentes:

  • Fila alta → problema de capacidade/roteamento (runners).
  • Execução alta → problema de build (cache, Docker layers, dependências, paralelismo).

1.1 Como medir sem ferramentas extras

  1. Abra o log do job e anote:

    • horário de criação
    • horário de início
    • horário de término
  2. Calcule:

    • fila = início - criação
    • execução = término - início
  3. Faça isso para 5 a 10 execuções (não só uma). Pipeline “lento às vezes” quase sempre é concorrência + runner compartilhado + variação de rede/hardware.

1.2 Regra de bolso para times pequenos

  • Se a fila for > 30% do tempo total, pare de “otimizar build” e comece por runners.
  • Se a execução for > 70% do tempo total, seu ROI maior tende a ser cache e Docker layer caching.

2) Runners: como evitar job no runner “errado”

O sintoma clássico é: “às vezes é rápido, às vezes fica 10 minutos esperando”.

Em geral isso acontece quando:

  • Falta critério para direcionar jobs (tags/labels).
  • Existe um runner “genérico” aceitando tudo (inclusive jobs pesados).
  • O pipeline depende de máquinas de desenvolvimento (Wi‑Fi, uso local, variação de hardware).

2.1 GitLab: tags de runner (e o que quase todo mundo erra)

No GitLab, tags de CI/CD servem para controlar quais jobs um runner pode executar (não confundir com Git tags de commits). Um job só roda em um runner que tenha todas as tags definidas no job. Se o runner estiver configurado para rodar apenas jobs com tag, jobs sem tag podem ficar “stuck”.

Leitura complementar (satélite):

Exemplo de job com tag dedicada:

build_web:
  stage: build
  tags:
    - linux-x64
    - docker
  script:
    - npm ci
    - npm run build

Boas práticas para time pequeno:

  • Use poucas tags, com significado claro (ex.: linux-x64, linux-arm64, macos, docker).
  • Evite tags “genéricas” que viram uma lixeira (ex.: build, runner1).
  • Defina um runner “padrão” apenas para jobs leves (lint/test rápido), e runners separados para builds pesados.

2.2 GitHub Actions: runs-on, labels e runner groups

No GitHub Actions, você direciona execução com runs-on. Para runners self-hosted, você pode usar labels e runner groups para segmentar quem roda o quê.

Exemplo simples (hosted runner):

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

Exemplo com runner self-hosted (labels):

jobs:
  build:
    runs-on: [self-hosted, linux, x64, build-heavy]
    steps:
      - uses: actions/checkout@v4
      - run: ./ci/build.sh

Leitura complementar (satélite):

3) Cache no CI/CD: dependências rápidas e builds previsíveis

Cache não é “acelerador mágico”. É um contrato.

  • Se o cache “acerta”, você economiza minutos.
  • Se o cache “erra”, você cria bugs difíceis (artefato antigo, dependência incompatível, build contaminado).

Leitura complementar (satélite):

3.1 Cache vs artifacts: não misture

  • Cache: reuso de dependências e diretórios que podem ser reconstruídos (ex.: cache do npm/pnpm, Gradle, pip).
  • Artifacts: saída do build que será consumida por outro job (ex.: pasta dist/, binários, relatórios).

Regra prática: se outro job precisa do arquivo “como resultado”, use artifact. Se é só para acelerar repetição, use cache.

3.2 GitHub Actions: key e restore-keys (o básico que resolve 80%)

Um bom cache key normalmente inclui:

  • SO e arquitetura
  • Hash do lockfile
  • Versão de runtime (quando necessário)

Exemplo para Node (cachê de dependências):

- name: Cache npm
  uses: actions/cache@v4
  with:
    path: ~/.npm
    key: ${{ runner.os }}-${{ runner.arch }}-npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-${{ runner.arch }}-npm-

Erros comuns:

  • Cachear diretórios “grandes e instáveis” (ex.: node_modules) sem critério.
  • Esquecer arquitetura no key (quebra quando você tem ARM e x64).
  • Incluir “o commit” no key (invalida toda vez e não serve para nada).

3.3 GitLab CI/CD: cache key e fallback_keys

No GitLab, você pode usar cache:key e chaves de fallback para aproveitar cache mesmo em branches novas (quando faz sentido).

Exemplo:

cache:
  key: "node-${CI_RUNNER_EXECUTABLE_ARCH}-${CI_COMMIT_REF_SLUG}"
  paths:
    - .npm/
  fallback_keys:
    - "node-${CI_RUNNER_EXECUTABLE_ARCH}-main"
    - "node-${CI_RUNNER_EXECUTABLE_ARCH}-default"

Dicas:

  • Separe cache por arquitetura (CI_RUNNER_EXECUTABLE_ARCH ou variável equivalente).
  • Use fallback para “pegar o cache da main” quando a branch é nova (bom para dependências).
  • Evite fallback para outputs de build (isso é artifact, não cache).

4) Docker layer caching: quando o build “come” o pipeline

Se seu pipeline faz docker build e demora muito, existem dois cenários:

  1. Você está rebuildando camadas que poderiam ser reaproveitadas.
  2. Seu Dockerfile invalida cache cedo (ex.: copia o repo inteiro antes de instalar dependências).

Leitura complementar (satélite):

4.1 Primeiro: conserte o Dockerfile

Checklist de ouro (multi-stage e cache friendly):

  • Copie primeiro apenas manifestos (ex.: package.json, package-lock.json).
  • Instale dependências.
  • Só depois copie o resto do código.
  • Separe build e runtime (multi-stage).

Exemplo (Node):

FROM node:20 AS build
WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html

4.2 Depois: use cache de layers no CI

Duas estratégias comuns:

  • Registry cache (recomendado): o cache vai e volta via registry.
  • Cache local (bom para runner dedicado): o runner mantém camadas localmente.

Um padrão simples é usar BuildKit/buildx com --cache-from e --cache-to apontando para o registry.

5) ARM64 vs x64: por que isso quebra cache e (às vezes) o build

Arquitetura não é detalhe.

Se você tem runners ARM64 e x64 rodando o mesmo pipeline, isso afeta:

  • Caches (dependências nativas não são compatíveis entre arquiteturas).
  • Docker images (precisam existir para a arquitetura ou usar emulação, que é mais lenta).
  • Binários pré-compilados (ex.: pacotes com addons nativos).

Diagrama: tags/labels e arquitetura

5.1 Regras práticas para não sofrer

  1. Inclua arquitetura no cache key.
  2. Tenha tags/labels explícitas por arquitetura.
  3. Se buildar Docker multi-arch, trate como pipeline separado (ou matrix com estratégia).

Exemplo (GitHub Actions com matrix de arch):

strategy:
  matrix:
    arch: [x64, arm64]

jobs:
  build:
    runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04' }}
    steps:
      - uses: actions/checkout@v4
      - run: ./ci/build.sh

Exemplo (GitLab com tags por arch):

build_amd64:
  tags: [linux-x64, docker]
  script: ["./ci/build.sh"]

build_arm64:
  tags: [linux-arm64, docker]
  script: ["./ci/build.sh"]

6) macOS runners: quando você precisa e como evitar armadilhas

macOS runner normalmente entra por necessidade:

  • build iOS/macOS (Xcode)
  • codesign/notarization
  • testes específicos de macOS

Leitura complementar (satélite):

6.1 Dicas rápidas

  • Evite rodar jobs “genéricos” no macOS: reserve para o que realmente precisa.
  • Prefira runner dedicado para macOS (custo alto por hora e fila dói).
  • Cacheie com cuidado (Xcode DerivedData pode ajudar, mas também pode contaminar se for compartilhado sem estratégia).

Checklist final: plano de 1 dia para acelerar seu CI/CD

  • Medir fila vs execução em 5 a 10 execuções.
  • Definir “classes” de jobs (leve, pesado, macOS, Docker build).
  • Criar tags/labels claras e remover runner “lixeira” que aceita tudo.
  • Separar cache por arquitetura (ARM64/x64) e por lockfile hash.
  • Ajustar Dockerfile para não invalidar cache cedo.
  • Ativar Docker layer caching (registry cache ou cache local em runner dedicado).
  • Documentar: “qual job roda em qual runner e por quê” (evita regressão).

Receba conteúdos práticos de tecnologia e IA

Referências

    Compartilhar

Inscreva-se em nossa newsletter

Posts semelhantes

Tempo de Leitura
3
min
GitLab CI: tags de runner e roteamento de jobs (sem dor)

Acelere a sua empresa com a X-Apps

Alocar profissionaisSolicitar Orçamento
A X-Apps é um provedor de TI parceiro e aconselhada pelo
Receba nossos e-mails
Siga nossas redes sociais
O seu time de TI. Desenvolvimento de software sob demanda e alocação de profissionais.
Vamos conversar?
comercial@x-apps.com.br11 5083-0122

Rua Rodrigo Vieira, 126

Jardim Vila Mariana. São Paulo, SP.

CEP: 04115-060

Mapa do site
Termos de serviçoTermos de privacidade
Available in English