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-lockfile

    Cache 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=max

    Paralelizar 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.

    Perguntas frequentes