Cache no GitHub Actions: acelere pipelines de CI/CD em até 5x

O npm install levava 3 minutos e 15 segundos.
Esse era o tempo de build da CI em um projeto que assumi no ano passado. A cada push, eu ficava olhando o log do GitHub Actions girar, esperando aparecer aquele check verde. Para ser sincero, naquela época eu sempre mudava para outra janela para matar o tempo — afinal, não havia nada a fazer além de esperar.
Depois que adicionei o cache, o mesmo build passou a terminar em 40 segundos. Quase 5 vezes mais rápido.
Não é mágica. É apenas uma estratégia de cache do GitHub Actions configurada corretamente. A seguir, reuni as armadilhas que encontrei, os dados que medi e modelos de configuração que você pode copiar e usar. Se você também vive esperando a CI terminar, isso pode economizar um bom tempo.
1. Conceitos centrais do mecanismo de cache
Primeiro, vale entender como o cache funciona. Sem isso, fica fácil errar na configuração.
O mecanismo de cache do GitHub Actions é bem simples e tem três etapas: buscar → restaurar → salvar. Você define uma key, e o GitHub procura um cache correspondente. Se encontrar, restaura o conteúdo diretamente no diretório de trabalho; se não encontrar, salva um novo cache quando o job termina.
Mas há alguns limites rígidos que você precisa conhecer:
| Limite | Valor |
|---|---|
| Limite de cache por repositório | 10 GB |
| Limite por arquivo de cache | 5 GB (na prática, arquivos acima de 1 GB já costumam causar problemas) |
| Período de retenção | Exclusão após 7 dias sem acesso |
| Limite global de uploads simultâneos | No máximo 5 caches enviados ao mesmo tempo |
Já vi gente esbarrar no limite de 10 GB: o projeto tinha dependências demais, o cache crescia sem parar e, no fim, os caches novos não eram salvos enquanto os antigos eram removidos. Cada build virava uma inicialização a frio.
Outro ponto que costuma confundir: Cache e Artifact não são a mesma coisa. Cache serve à CI e prioriza velocidade. Artifact é destinado às pessoas, como os artefatos de build e relatórios de teste, e precisa ficar armazenado por mais tempo. O Cache tem limite de 10 GB; o Artifact não tem esse limite, embora consuma o armazenamento do repositório.
Também existe o Docker Layer Cache, específico para builds Docker. A lógica é um pouco diferente do cache comum e terá uma seção própria mais adiante.
2. Estratégias para projetar chaves de cache
A taxa de acerto do cache depende inteiramente do desenho da key. Esse é o ponto central de toda a estratégia.
O que é hashFiles()
O GitHub oferece a função integrada hashFiles(), que calcula o hash de um arquivo. Ela costuma ser usada com package-lock.json ou yarn.lock: se as dependências não mudarem, o hash também não muda e o cache pode ser reutilizado.
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
Esse trecho gera uma chave no formato npm-Linux-a1b2c3d4e5f6.... Enquanto o package-lock.json não mudar, a chave permanece igual.
restore-keys: uma alternativa de recuperação
As dependências acabam mudando, e é aí que entra restore-keys. Ele funciona como um mecanismo de correspondência gradual:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-{{ runner.os }}-
Primeiro, o GitHub tenta encontrar a key completa. Não encontrou? Então procura um cache antigo cujo nome comece com npm-Linux-. Mesmo sem um acerto exato, a maioria dos pacotes já estará em node_modules, e só será necessário instalar as novas dependências de forma incremental.
Comparação entre três padrões de chave
Nos meus testes, estes são os três padrões que recomendo:
Padrão simples (para projetos pequenos):
key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
Padrão com versão (para várias versões do Node):
key: {{ runner.os }}-node{{ matrix.node-version }}-{{ hashFiles('**/package-lock.json') }}
Padrão com vários caminhos (para monorepos):
key: {{ runner.os }}-{{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
Como saber se houve acerto de cache
actions/cache expõe uma variável de saída chamada cache-hit:
- uses: actions/cache@v4
id: cache-npm
with:
path: ~/.npm
key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
- name: Check cache hit
run: echo "Cache hit - {{ steps.cache-npm.outputs.cache-hit }}"
true indica um acerto exato; false, um acerto parcial ou uma falha completa. Você pode usar essa variável para decidir se deve executar npm ci:
- name: Install dependencies
if: steps.cache-npm.outputs.cache-hit != 'true'
run: npm ci
3. Exemplos práticos de configuração
Com a teoria esclarecida, vamos ao código. Testei todas as configurações abaixo, e elas podem ser copiadas diretamente.
Cache do npm (prefira setup-node)
Na verdade, o setup-node já oferece cache integrado e é mais simples do que configurar actions/cache manualmente:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm' # ou 'yarn', 'pnpm'
Uma linha resolve. Mas, se você quiser armazenar outro diretório em cache, como node_modules, ainda precisará usar actions/cache:
- uses: actions/cache@v4
with:
path: node_modules
key: {{ runner.os }}-nm-{{ hashFiles('**/package-lock.json') }}
restore-keys: {{ runner.os }}-nm-
Minha recomendação: prefira o cache integrado do setup-node, a menos que você tenha uma necessidade específica.
yarn e pnpm
O diretório de cache do yarn é diferente do npm:
- uses: actions/cache@v4
with:
path: |
~/.yarn/cache
~/.yarn/install-state.gz
key: yarn-{{ runner.os }}-{{ hashFiles('**/yarn.lock') }}
O pnpm tem outra particularidade: ele usa um store global.
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-{{ runner.os }}-{{ hashFiles('**/pnpm-lock.yaml') }}
Cache do Python/pip
Este é o caminho de cache para projetos Python:
- uses: actions/cache@v4
with:
path: ~/.cache/pip
key: pip-{{ runner.os }}-{{ hashFiles('**/requirements.txt') }}
restore-keys: pip-{{ runner.os }}-
Docker Layer Cache
O build de imagens Docker costuma ser a etapa mais demorada. A boa notícia é que o BuildKit oferece suporte ao backend de cache do GitHub Actions:
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
context: .
push: false
cache-from: type=gha
cache-to: type=gha,mode=max
type=gha indica que as camadas Docker devem ser armazenadas no serviço de cache do GitHub Actions. Nos meus testes, o build de uma imagem que levava 5 minutos caiu para cerca de 1 minuto.
Cache de módulos Go
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
~/.cache/go-build
key: go-{{ runner.os }}-{{ hashFiles('**/go.sum') }}
Cache do Rust Cargo
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-{{ runner.os }}-{{ hashFiles('**/Cargo.lock') }}
A compilação do Rust é demorada, e o cache pode economizar bastante tempo. Mas cuidado: o diretório target cresce continuamente, então é recomendável limpá-lo periodicamente.
4. Otimização de desempenho e boas práticas
Reuni alguns dados de testes e armadilhas que encontrei para ajudar você a evitar os mesmos problemas.
Dados de benchmark
Segundo o relatório de testes da RunsOn, atualizado em janeiro de 2026, uma configuração adequada de cache produz os seguintes resultados:
| Operação | Sem cache | Com cache | Ganho |
|---|---|---|---|
| npm install | 3 minutos | 40 segundos | cerca de 5x |
| yarn install | 2 minutos e 30 segundos | 35 segundos | cerca de 4x |
| Docker build | 5 minutos | 1 minuto | cerca de 5x |
| pip install | 45 segundos | 8 segundos | cerca de 5x |
A taxa de acerto fica entre 70% e 90%, dependendo da qualidade da sua estratégia de chaves.
Armadilhas comuns
Não armazene node_modules diretamente no cache
Foi exatamente o que fiz no início, e acabei enfrentando um problema sério.
# Não faça isso
path: node_modules
node_modules depende da plataforma: pacotes instalados no Linux podem apresentar problemas no Windows. O correto é armazenar o diretório de cache global, como ~/.npm, e deixar o npm ci montar as dependências.
Use GNU tar + zstd para cache entre sistemas operacionais
Por padrão, o formato de tar varia entre macOS e Windows, o que pode causar falhas na restauração do cache. Adicione esta configuração:
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
enableCrossOsArchive: true
Problemas de cache contaminado
Às vezes, o cache contém uma dependência com problema e faz o build falhar continuamente. Há duas soluções:
- Exclua o cache manualmente: abra a página Actions → Caches do repositório no GitHub e clique em excluir
- Force a atualização da key: adicione um prefixo ou timestamp para gerar uma nova chave
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
Checklist de boas práticas
Para encerrar, aqui estão alguns pontos que você pode conferir antes de configurar:
- Dê preferência ao cache integrado das actions oficiais (
setup-node,setup-python) - Inclua hashFiles na key; caso contrário, o cache antigo continuará sendo usado mesmo após uma atualização das dependências
- Configure restore-keys; a correspondência gradual pode salvar o build
- Não armazene node_modules no cache; armazene o diretório global
- Exclua caches antigos periodicamente para não ultrapassar o limite de 10 GB
5. Perguntas frequentes
P1: Por que minha taxa de acerto de cache é baixa?
O motivo mais comum é uma key que muda com frequência. Se você inclui um timestamp ou o nome da branch, por exemplo, cada push gera uma key nova. A solução é usar apenas runner.os e hashFiles, removendo variáveis desnecessárias.
Outro motivo é hashFiles incluir arquivos que não deveria. Se você usa hashFiles('**/*.json'), uma simples mudança em um arquivo de configuração invalida o cache. Restrinja a correspondência a package-lock.json ou yarn.lock.
P2: O que fazer quando o espaço de cache ultrapassa o limite?
10 GB parece muito, mas um monorepo ou cache Docker pode consumir esse espaço rapidamente. Soluções:
- Limpeza periódica: exclua manualmente os caches antigos em GitHub Actions → Caches
- Caches separados: use uma key diferente para cada tipo de dependência, em vez de guardar tudo em um único cache
- Use self-hosted runners: eles não têm o limite de 10 GB
P3: self-hosted runners exigem alguma configuração especial?
Não. O mecanismo de cache funciona da mesma forma. A vantagem dos self-hosted runners é que o cache fica armazenado localmente, sem latência de transferência pela rede, o que acelera a restauração. A desvantagem é que o cache não é limpo automaticamente; você precisa criar um script para fazer essa limpeza periodicamente.
P4: Como forçar uma atualização do cache?
Altere a key. Adicione um número de versão como prefixo:
key: npm-v3-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
Outra opção é excluir o cache antigo para que o sistema gere um novo.
Conclusão
Depois de tudo isso, a ideia central cabe em uma frase: um cache bem configurado pode deixar a CI 5 vezes mais rápida.
Vamos fazer as contas: se cada build economiza 2 minutos e você executa 10 builds por dia, são 600 minutos por mês, ou cerca de 10 horas. Tempo suficiente para escrever vários artigos.
Se você está começando com GitHub Actions, use primeiro o cache integrado do setup-node. Uma linha de configuração basta. Quando encontrar um gargalo, volte para estudar estratégias de chave mais avançadas e o Docker Layer Cache.
Este é o terceiro artigo da série de guias práticos de GitHub Actions. Os anteriores abordam a criação de pipelines de CI e estratégias de implantação; se o tema interessar, vale consultar os artigos anteriores.
No próximo push, observe o tempo do build. Veja se ele cai de 3 minutos para 40 segundos — basta testar.
Configurar o cache do GitHub Actions para acelerar o CI/CD
Configure o cache do GitHub Actions para reduzir o tempo do npm install de 3 minutos para 40 segundos
⏱️ Estimated time: 10 min
- 1
Step 1: Escolher a estratégia de cache
Escolha a estratégia de cache de acordo com o gerenciador de pacotes do projeto:
• Projetos npm: dê preferência ao cache integrado do setup-node
• Projetos yarn/pnpm: configure o caminho do cache
• Builds Docker: use o backend gha do BuildKit - 2
Step 2: Projetar a chave de cache
Use hashFiles() com base no arquivo de lock para gerar uma chave estável:
• Padrão básico: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
• Adicione restore-keys como correspondência alternativa
• Evite incluir timestamp ou nome de branch na key - 3
Step 3: Adicionar a configuração de cache
Adicione uma etapa de cache ao arquivo de workflow:
• npm: use actions/setup-node@v4 com cache: 'npm'
• Caminhos personalizados: use actions/cache@v4
• Docker: configure cache-from e cache-to - 4
Step 4: Validar o efeito do cache
Verifique se houve acerto de cache:
• Consulte a variável de saída cache-hit (true indica acerto exato)
• Compare o tempo de build (a redução esperada é de 4 a 5 vezes)
• Confira a página Actions → Caches para confirmar que o cache foi armazenado - 5
Step 5: Fazer a manutenção periódica do cache
Evite problemas com o cache:
• Monitore o espaço usado pelo cache (limite de 10 GB)
• Exclua caches antigos periodicamente
• Em caso de cache contaminado, altere o prefixo da key para forçar a recriação
FAQ
Por que minha taxa de acerto de cache é de apenas 30%?
O que acontece quando o cache ultrapassa 10 GB?
• Separe tipos diferentes de dependência em caches distintos, com uma key para npm, outra para Docker e outra para pip
• Exclua periodicamente caches desnecessários na página Actions → Caches
• Em projetos monorepo, avalie separar repositórios ou usar self-hosted runners
Branches diferentes podem compartilhar o cache?
O que muda no cache de um self-hosted runner?
Uma falha ao restaurar o cache interrompe o build?
Como saber quando o cache precisa ser atualizado?
• Mudança na versão das dependências: hashFiles cuida disso automaticamente, sem intervenção manual
• Cache contaminado: o build começa a falhar de repente e é preciso excluir o cache antigo
• Mudança de configuração: em uma atualização do Node, por exemplo, inclua o número da versão na key
Na maioria dos casos, uma configuração correta dispensa gerenciamento manual.
9 min de leitura · Publicado em: 7 abr 2026 · Atualizado em: 4 set 2026
Guia completo GitHub Actions
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
GitHub Actions CI: pipeline de build e testes do zero
Monte uma pipeline de CI com GitHub Actions, configure gatilhos e permissões, use cache e teste várias versões do Node.js em paralelo com Matrix.
Parte 2 de 6
Próximo
GitHub Actions Matrix: testes paralelos em várias versões na prática
Tutorial prático de GitHub Actions Matrix, com sintaxe básica, filtros exclude/include, otimização de fail-fast e controle de recursos com max-parallel, além de um pipeline completo para testes paralelos em várias versões.
Parte 4 de 6



Comentários
Entre com GitHub para comentar