Cache de dependências no GitHub Actions
Um pipeline de CI que roda npm install do zero a cada commit desperdiça 2-3 minutos por run. Com cache de node_modules, Docker layers e artefatos de build, o mesmo pipeline roda em menos de 1 minuto — e dentro dos 2.000 minutos gratuitos do GitHub Actions.
Cache automático do setup-node
A forma mais simples de fazer cache do npm/yarn/pnpm:
yaml
# .github/workflows/ci.yml
# O actions/setup-node tem cache nativo:
- name: Configurar Node.js com cache
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm' # 'npm', 'yarn' ou 'pnpm'
# cache-dependency-path: '**/package-lock.json' # para monorepo
# Com cache ativo:
# - Primeiro run: npm ci completo (~2 min)
# - Runs seguintes: cache hit → npm ci em ~10s
# O setup-node usa o package-lock.json como chave de cache:
# Se o lock não mudar → cache hit
# Se qualquer dependência mudar → cache miss → npm ci completo
# Para pnpm (mais rápido que npm para CI):
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- run: pnpm install --frozen-lockfileCache manual com actions/cache
Cache com controle total sobre a chave e os caminhos:
yaml
# .github/workflows/ci.yml — cache manual:
- name: Cache node_modules
uses: actions/cache@v4
id: cache-node
with:
path: |
~/.npm
node_modules
# Para monorepo:
packages/*/node_modules
key: node-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
node-${{ runner.os }}-
- name: Instalar dependências
if: steps.cache-node.outputs.cache-hit != 'true'
run: npm ci
# Cache do build TypeScript (tsc incremental):
- name: Cache build TypeScript
uses: actions/cache@v4
with:
path: |
dist/
.tsbuildinfo
key: tsc-${{ runner.os }}-${{ hashFiles('src/**/*.ts', 'tsconfig.json') }}
- name: Build TypeScript
run: npx tsc --incremental
# Cache para Vitest (resultados de testes não mudados):
- name: Cache Vitest
uses: actions/cache@v4
with:
path: .vitest/cache
key: vitest-${{ runner.os }}-${{ hashFiles('src/**') }}Cache de layers Docker
Acelerar builds Docker com cache de layers:
yaml
# .github/workflows/docker.yml — cache Docker com GitHub Actions:
- name: Configurar Docker Buildx
uses: docker/setup-buildx-action@v3
# Opção 1: cache local no runner (mais simples):
- name: Cache Docker layers (local)
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: buildx-${{ runner.os }}-${{ github.sha }}
restore-keys: buildx-${{ runner.os }}-
- name: Build e push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/org/app:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Rotacionar cache (evitar crescimento)
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
# Opção 2: cache no GHCR (persiste entre runners, mais confiável):
- name: Build e push com cache no registry
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/org/app:latest
cache-from: type=registry,ref=ghcr.io/org/app:buildcache
cache-to: type=registry,ref=ghcr.io/org/app:buildcache,mode=maxParalelizar jobs para reduzir tempo total
Executar testes, lint e build em paralelo:
yaml
# .github/workflows/ci.yml — jobs paralelos:
jobs:
# Instalar dependências uma vez e compartilhar via artifact:
install:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- uses: actions/upload-artifact@v4
with:
name: node_modules
path: node_modules
retention-days: 1
# Lint, type check e testes rodando em paralelo:
lint:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: node_modules
path: node_modules
- run: npm run lint
typecheck:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: node_modules
path: node_modules
- run: npx tsc --noEmit
test:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: node_modules
path: node_modules
- run: npm test
# Deploy só após todos passarem:
deploy:
needs: [lint, typecheck, test]
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "Deploy!"Medir e otimizar o tempo de cada job
Identificar gargalos no pipeline com timing explícito:
bash
# .github/workflows/ci.yml — adicionar timing:
- name: Medir tempo de npm ci
run: |
START=$(date +%s)
npm ci
END=$(date +%s)
echo "npm ci levou $((END-START))s"
echo "cache-status=${{ steps.cache-node.outputs.cache-hit }}" >> $GITHUB_STEP_SUMMARY
# Relatório de sumário no PR:
- name: Resumo do CI
if: always()
run: |
echo "## Resultado do CI" >> $GITHUB_STEP_SUMMARY
echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| TypeScript | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Testes | ✅ |" >> $GITHUB_STEP_SUMMARY
echo "| Cache hit | ${{ steps.cache-node.outputs.cache-hit }} |" >> $GITHUB_STEP_SUMMARY
# Benchmarks típicos com cache bem configurado:
# npm ci sem cache: 90-180s
# npm ci com cache: 5-15s
# Docker build sem cache: 120-300s
# Docker build com cache: 15-45s
# TypeScript check: 10-30s
# Vitest (unitários): 5-30s
# Total do pipeline:
# Sem cache: 8-12 min → com cache: 1-3 min$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS para APIs em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.