CI/CD & DevOps65 min total · 17 parts
CI/CD Pipelines Explained: From Push to Production
Part 8 of 17 · ~1 min
Caching Dependencies
For the first two weeks, every single job that runs npm ci pays the full cost of downloading snapledger's entire dependency tree from scratch — nothing shared, nothing remembered, run after run after run. Marcus times it out of curiosity: eighty-some seconds, every time, on a project where almost nothing in package-lock.json has changed since Tuesday.
- uses: actions/cache@v4
with:
path: ~/.npm
key: web-npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
web-npm-
- run: npm ci
The key is a fingerprint of the lockfile. Change nothing in it and every run since gets a cache hit — Docker's registry never even sees a fresh pull for most of it, npm ci restores from what's already sitting on the runner's cache volume, and the eighty seconds drops to roughly seven. restore-keys is the soft fallback: add one new dependency and the exact key no longer matches anything, but the prefix web-npm- still does, so Docker — sorry, so npm ci — restores the most recent close match and only has to fetch whatever's actually new, instead of starting over from zero.
It's worth naming out loud that this is the same trick this team already learned once, just one layer up: a Dockerfile skips rebuilding a layer whose inputs are unchanged, and this skips redownloading a dependency tree whose lockfile is unchanged. Get the fingerprint wrong in either direction and it misbehaves the same way both places — hash something too forgiving and you're quietly handed a stale install nobody asked for; hash something too twitchy and you never get a hit at all, just extra bookkeeping bolted onto the full download you were trying to avoid.