Cut Build Times in Half: Mastering CircleCI Cache Keys for Dependency Installation
Learn how to design cache keys that automatically refresh when lockfiles change, keep cache size in check, and avoid stale artifacts—so your CI jobs run faster and more reliably.
05 Feb 2026, 14:56 UTC

Problem Statement
Every CI pipeline spends a noticeable chunk of time downloading and installing dependencies. In a typical JavaScript or Java project, this can take 2–5 minutes on a clean machine. When you run the same job in parallel or across multiple branches, the same heavy work repeats, wasting CI minutes and cloud credits.
Thesis
CircleCI’s built‑in caching can cut dependency install times from minutes to seconds—if you design the cache key correctly, keep the cache size within limits, and restore it in the right order.
1. CircleCI Cache Basics
CircleCI stores cache entries per project in a key/value store. A cache is identified by a key string you supply in the workflow YAML. The key can contain literals, environment variables, and hash fragments. When a job starts, CircleCI looks for an existing cache with that key. If found, it restores the cached files to the working directory before any steps run. After the job finishes, you can optionally save a new cache.
Key points:
- Cache keys are deterministic—the same key always refers to the same cache.
- Cache size is capped at 1 GB per project by default; you can raise it in project settings.
- Cache entries are not encrypted; never cache secrets.
- Cache entries are shared across branches; careful key design prevents stale artifacts.
2. Designing a Robust Cache Key
The cache key should change only when the cached artifacts need to be refreshed. A common pattern is to hash the lockfile that pinpoints the exact dependency versions. For example:
cache_key: "{{ checksum "package-lock.json" }}-node-v14"
Here the key is deterministic: if package-lock.json changes, the checksum changes and a new cache is created. If the lockfile stays the same, subsequent jobs hit the same cache.
Avoid overly broad keys (e.g., a static string) because you’ll never reuse the cache after the first run. Avoid overly narrow keys (e.g., including timestamps) because you’ll miss reuse opportunities.
When you have multiple dependency managers, you can combine hash fragments:
cache_key: "{{ checksum "pom.xml" }}-{{ checksum "requirements.txt" }}-java-11"
Each part of the key is a hash of a file that governs a set of dependencies. This keeps the cache granular and avoids unnecessary invalidations.
3. Practical Example: npm in a Node.js Project
Below is a minimal .circleci/config.yml that demonstrates a full cache cycle for a Node.js project using npm install. The example assumes you have a package-lock.json in the repository root.
version: 2.1
orbs:
node: circleci/node:5.0.0
jobs:
build:
docker:
- image: cimg/node:18.12.0
steps:
- checkout
# Restore cache before installing
- restore_cache:
keys:
- "node-deps-{{ checksum "package-lock.json" }}"
- "node-deps-" # fallback
- run:
name: Install deps
command: npm ci
- run:
name: Run tests
command: npm test
# Save cache after install
- save_cache:
paths:
- ./node_modules
key: "node-deps-{{ checksum "package-lock.json" }}"
workflows:
version: 2
build_and_test:
jobs:
- build
Key observations:
- The
restore_cachestep appears beforenpm ciso that the cache is available when dependencies are installed. - The
save_cachestep uses the same key as the restore step, ensuring the next job will pick up the freshly installednode_modules. - The fallback key (just
node-deps-) ensures that if the first key fails (e.g., first run), the job still proceeds.
After the first run, you should see a log line like:
Restoring cache with key: node-deps-b1a2c3d4
On subsequent runs, a Cache hit message confirms that the node_modules folder was restored, skipping the network download.
4. Trade‑offs & Limitations
| Aspect | Benefit | Risk / Caveat |
|---|---|---|
| Speed | Dependency install from cache can be dramatically faster. | Cache miss on first run or key change. |
| Size | Up to 1 GB per project by default. | Exceeding triggers eviction of least‑recently used entries. |
| Staleness | Deterministic key keeps cache fresh. | Wrong key design can lead to stale or cross‑branch contamination. |
| Security | Cache is shared across jobs. | Never cache sensitive data; caches are not encrypted. |
When you need to store large caches (e.g., Maven ~/.m2), consider splitting the cache into multiple keys: one for core dependencies, another for optional plugins. This reduces eviction risk and keeps the cache size manageable.
Actionable Closing
To get the most out of CircleCI caching:
- Hash lockfiles in your cache key to trigger invalidation only when dependencies change.
- Place cache paths in the working directory so that the restore step can find them.
- Run a baseline build, then enable caching and compare the
npm ciduration in the job summary. - Check the job logs for "Restoring cache" and "Cache hit" messages to confirm cache usage.
- Monitor cache size in the CircleCI UI; if you hit the 1 GB limit, prune unused files or raise the quota.
By following these steps, you’ll reduce CI run times, save compute credits, and keep your pipelines predictable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.