BlogCI/CD

Speed up GitHub Actions with dependency caching

Cache node_modules, pip wheels, and Go modules keyed on your lockfile hash, and warm the cache on main to cut minutes off every GitHub Actions run.

May 12, 2026·6 min readBeginner

If every GitHub Actions run reinstalls dependencies from scratch, you are paying for the same download on every push. The actions/cache action stores a directory keyed on your lockfile and restores it next run — a one-line change that often halves wall-clock time.

bash — the differencelive
cold run:
npm ci ............ 1m48s
warm cache:
npm ci ............ 12s (cache hit)

Key the cache on your lockfile

The key decides when the cache is reused. Hash the lockfile so it invalidates the moment dependencies change, and add a restore-keys fallback so a near-miss still restores most of it.

.github/workflows/ci.yml
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
Cache the download dir, not node_modules
Caching ~/.npm (or the pip / Go module cache) is safer than caching node_modules — you still run a real install, so postinstall scripts and platform binaries stay correct.

The setup actions already do this

For common languages you do not even need the cache action — the setup-* actions have it built in.

.github/workflows/ci.yml
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm # restores + saves ~/.npm automatically
Caches are scoped and trusted
A cache written on a branch can be restored by PRs from it. Never cache secrets or build outputs you then trust implicitly — treat cache contents as untrusted input.
Go deeper in a courseSecure CI/CD with GitLabPipeline patterns that generalize — caching, scanning, and gates.View course

Related posts