Speeding Up CircleCI Builds with Checksum‑Based Dependency Caching
Learn how to use checksum‑based restore_cache and save_cache in CircleCI 2.1 to reuse dependencies across builds, with a concrete Node.js example, trade‑offs, and verification steps.
29 Nov 2025, 21:45 UTC

Why every commit feels like a fresh start
CircleCI jobs run in brand‑new containers, so a Node or Maven job spends minutes reinstalling the same dependencies on every push. That repetition wastes pipeline minutes and slows feedback.
Thesis: checksum‑keyed caches give precise, automatic invalidation
By wrapping the install step with restore_cache and save_cache and building the cache key from a lockfile checksum, you get a cache that is reused until the lockfile changes, then a fresh cache is written under a new key.
How the keys work
The {{ checksum "file" }} template hashes a single file at runtime. A typical key looks like:
key: v1-npm-deps-{{ checksum "package-lock.json" }}
If that exact key exists, CircleCI restores the cache. If not, it walks through an ordered list of fallback keys. Adding a prefix after the exact key gives a safety net:
keys:
- v1-npm-deps-{{ checksum "package-lock.json" }}
- v1-npm-deps- # fallback to most recent cache with this prefix
Caches are immutable: saving under an existing key does nothing. To force a fresh cache you bump the version prefix (e.g., v2-).
Worked example: Node.js project
Place the cache steps around npm ci in .circleci/config.yml (config version 2.1).
version: 2.1
jobs:
build:
docker:
- image: cimg/node:20.10
steps:
- checkout
- restore_cache:
keys:
- v1-npm-deps-{{ checksum "package-lock.json" }}
- v1-npm-deps-
- run:
name: Install dependencies
command: npm ci
- save_cache:
key: v1-npm-deps-{{ checksum "package-lock.json" }}
paths:
- ~/.npm
- node_modules
- run:
name: Run tests
command: npm test
workflows:
version: 2
build:
jobs:
- build
Where to run validation: on your local machine with the CircleCI CLI installed (circleci config validate .circleci/config.yml). You need read access to the config file; the command returns exit code 0 if the syntax is correct. A risk is that a typo in the key will cause a cache miss, leading to longer builds but no broken build.
Trade‑offs and limitations
- Full rebuild on any lockfile change: the exact‑checksum key forces a new cache, so even a minor version bump loses the previous cache entirely.
- Fallback may restore stale content: the prefix key can return a cache built from an older lockfile, which might include outdated or incompatible dependencies.
- Size and retention: each project’s caches are subject to size limits and eviction policies that can change; check the current CircleCI documentation for exact numbers.
- Platform specificity: caching native modules (e.g.,
node_moduleswith binary addons) across different Docker executors or architectures can break builds. - Not a secret store: never place environment variables, credentials, or other secrets in cached paths.
Actionable closing: verify and iterate
- Add the cache steps as shown above and push a commit.
- Open the job page on CircleCI and compare the “Duration” breakdown with and without caching (you can temporarily comment out the cache steps for a baseline).
- Run
circleci config process .circleci/config.ymllocally to see the expanded configuration and confirm the keys are evaluated as expected. - To test immutability, run a second workflow with the same lockfile; the second
save_cachestep should be skipped (visible in the job logs as “Cache already exists”). - If you need a genuinely fresh cache, bump the version prefix in the key (
v1-→v2-) and push a trivial change. - When debugging, you can clear all caches for a project from the CircleCI UI under Settings → Caches or via the API.
By following these steps you turn repetitive dependency installs into a fast, repeatable cache hit, saving minutes on every build while keeping the invalidation logic automatic and transparent.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.