Cache Hit vs. Stale Dependency Resolution: Balancing Build Speed and Security in Netlify Deployments
0 reputation · 31 Oct 2021, 05:09 UTC
0 reputation · 31 Oct 2021, 05:09 UTC
Build logs indicate a cache hit even though the package‑lock.json file has not been updated, suggesting that dependency resolution may be using outdated artifacts.
The free tier imposes a 1,000 build minute limit and a 1 GB cache ceiling, so reusing cache can extend build time but may hide security‑relevant dependency changes.
Should cache invalidation be triggered by any change to package‑lock.json, or only by direct dependency updates? Is it safe to rely on cache hits when transitive npm updates occur without a lock file change? How can you confirm that a cache hit does not mask outdated dependencies before deployment?
Cache invalidation should trigger on any change to package-lock.json. Netlify's build cache keys off the lock file's content hash. A hash change forces a fresh npm ci, which re-resolves the exact versions recorded. Manual edits, dependency additions, or version bumps all change the hash and invalidate the cache automatically.
Relying on cache hits is safe for reproducibility. When the lock file hash matches, the restored node_modules tree is bit-for-bit identical to the prior build. Transitive dependency versions cannot drift without a lock file change because npm ci (which Netlify runs) refuses to mutate a satisfied lock file.
The security risk is a stale lock file, not the cache. If upstream publishes a patched version within your declared ranges, package-lock.json won't update until you run npm update or delete node_modules and reinstall. The cache simply restores whatever the lock file currently pins.
Netlify stores ~/.npm and node_modules under a cache key derived from sha256(package-lock.json) (or yarn.lock). On each build:
npm ci — which sees a complete node_modules matching the lock file and exits near-instantly.npm ci and save the result (cache miss).The 1 GB LRU cache evicts least-recently-used entries. Frequent builds keep the cache warm but never alter resolved versions.
# Add as a build command before npm ci
echo \"CACHE_KEY: $(cat package-lock.json | sha256sum | cut -d' ' -f1)\"
Compare this value across deploys in the build logs. Identical hashes = identical dependency trees.
# Add as a build command after npm ci
npm audit --audit-level=high --json > audit-report.json || true
# Parse and fail if new high/critical findings since last known-good baseline
# (requires a small script or Netlify plugin to persist baseline)
This validates the lock file contents, not the cache mechanism.
Create a Netlify build hook (Settings → Build & deploy → Build hooks) and schedule a weekly cron (GitHub Actions, GitLab CI, or cron-job.org) that triggers it with NETLIFY_CACHE_DISABLE=1:
curl -X POST \"https://api.netlify.com/build_hooks/YOUR_HOOK_ID\" \
-H \"Content-Type: application/json\" \
-d '{\"env\":{\"NETLIFY_CACHE_DISABLE\":\"1\"}}'\
This runs npm ci from scratch and surfaces advisories against the current lock file.
.nvmrc or package.json#engines.node locks the runtime. Netlify's build image updates do not invalidate the cache; a Node bump can change resolution semantics.node_modules..npmrc contains tokens, they are cached. Expired tokens can cause silent fallback to the public registry and version drift. Rotate tokens and invalidate cache on rotation.npm ci --prefer-offline with a damaged local tarball cache can restore stale artifacts. Netlify isolates cache per site, reducing risk.Are you using a monorepo with multiple package-lock.json files (e.g., npm workspaces, pnpm, or Yarn workspaces)? If yes, the single cache key behavior changes the recommendation — you'll need a custom cache key strategy (e.g., hash all lock files together) or separate build steps per workspace.
Use comments to ask for clarification. Post a solution as an answer.
35,775 reputation · 31 Oct 2021, 12:19 UTC
Netlify generates its build cache key from a hash of the commit SHA and the package‑lock.json file, so any modification to the lock file creates a new key and forces fresh dependency resolution. When only transitive npm updates occur without a lock‑file change, the same key is reused, meaning the build may pull cached packages that are now out‑of‑date. To verify a cache hit does not mask stale dependencies, inspect the log’s cache‑key hash and compare it to the SHA‑256 of the current package‑lock.json; identical hashes confirm a fresh resolution, while a mismatch indicates stale artifacts.