Know what you build with
Use lockfiles deliberately
Commit and review the ecosystem lockfile so local, CI, and production builds resolve the same dependency graph.
A version range describes allowed updates. A lockfile records the exact graph selected for this application.
When package.json says "express": "^4.18.0", you are telling npm that any 4.x version at or above 4.18.0 is acceptable. Which one you actually get depends on what the registry offers at install time. The lockfile (package-lock.json for npm) freezes that answer: exact versions, resolved URLs, and an integrity hash for every package in the tree.
Here is why that matters. A developer adds mailer with a version range and reviews version 3.2. CI resolves 3.3 the next morning because the lockfile was not committed. The source did not change, but the code entering the build did. If 3.3 was a compromised release, it shipped without anyone looking at it.
Install deterministically in CI
npm install may update the lockfile. npm ci never does:
npm ci
It deletes node_modules, installs exactly what the lockfile says, verifies each package against its recorded integrity hash, and fails if package.json and package-lock.json disagree. That failure is a feature: it means someone changed a range without regenerating the lock.
Add a guard so CI also catches an install step that silently modified the lockfile:
git diff --exit-code package-lock.json
A non-zero exit means the file changed during the build. Fail the job and investigate.
Review lockfile changes like code
Regenerate the lockfile through the package manager, never by editing integrity or resolved fields by hand. Review the lockfile diff together with the manifest change that caused it. A one-line dependency bump that rewrites two thousand lockfile lines deserves a question, not an approval reflex.
Know what the lockfile does not cover
A frozen lockfile prevents resolution drift. It does not cover a script that downloads latest.zip at install time, and it does not control your container base image. It also does not prove that a locked package is trustworthy — it only guarantees you get the same bytes every time.
Run the frozen install command from a clean checkout and save the resolved dependency tree. Change one manifest range without updating the lockfile and show that the command fails. Also identify one build input, such as a remote binary or container base, that the lockfile does not control.
Lesson completed