Lockfiles & Reproducible Installs
Lockfiles pin the exact dependency graph so laptops, CI, and production builds install identical packages every time.
Recipe
Quick-reference recipe card - copy-paste ready.
# Developer: update deps intentionally
npm install
git add package.json package-lock.json
# CI: reproducible clean install
npm ci# .github/workflows/ci.yml (excerpt)
- run: npm ci
- run: npm testWhen to reach for this:
- CI must not resolve new versions on every pipeline run.
- Production Docker builds need deterministic
node_modules. - Security audits must reference the same versions you ship.
Working Example
# Dockerfile - multi-stage API build
FROM node:24.18.0-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
FROM node:24.18.0-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24.18.0-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
CMD ["node", "dist/server.js"]# Local verify: lockfile matches package.json
npm ci
npm testWhat this demonstrates:
npm cideletesnode_modulesand installs exactly frompackage-lock.json.- Docker
depsstage uses--omit=devfor a smaller production image. - Build stage runs full
npm ciso devDependencies (TypeScript) are available to compile.
Deep Dive
How It Works
package-lock.json(npm) records resolved versions, integrity hashes, and nested structure.npm installmay update the lockfile when ranges allow newer compatible versions.npm cifails ifpackage.jsonand lockfile disagree, protecting CI from drift.- pnpm uses
pnpm-lock.yaml; Yarn usesyarn.lock- only one per repo.
Manager Commands
| Manager | Frozen install command | Lockfile |
|---|---|---|
| npm 10+ | npm ci | package-lock.json |
| pnpm | pnpm install --frozen-lockfile | pnpm-lock.yaml |
| Yarn Berry | yarn install --immutable | yarn.lock |
TypeScript Notes
{
"scripts": {
"verify:lockfile": "npm ci && npm run typecheck && npm test"
}
}- Run
verify:lockfileafter merging dependency PRs to catch missing native rebuilds. - Commit lockfile changes in the same PR as
package.jsonedits.
Gotchas
- Running npm install in CI - Resolves fresh versions and hides lockfile drift until production. Fix: use
npm ciexclusively in pipelines. - Partial lockfile commits - Updating
package.jsonwithout the lockfile breaks teammates and CI. Fix: always commit both files together. - Mixed managers -
package-lock.jsonplusyarn.lockcauses unpredictable installs. Fix: delete unused lockfiles; document the chosen manager in README. - Lockfile merge conflicts - Manual edits corrupt integrity entries. Fix: regenerate with
npm installafter resolvingpackage.json. - Global cache masking issues - Stale CI caches serve old tarballs. Fix: key caches on lockfile hash; bust on lockfile change.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
npm install --package-lock-only | Audit resolution without touching node_modules | You need to verify runtime behavior |
| Renovate / Dependabot | Automated lockfile PRs with tests | You lack CI to validate bumps |
| Volta / nvm pin | Same Node + npm across machines | Replacing lockfiles entirely |
FAQs
What is the difference between npm install and npm ci?
npm installmay update the lockfile and reuse existingnode_modules.npm cirequires a lockfile, removesnode_modules, and installs exactly pinned versions.- CI should use
npm ci; developers usenpm installwhen intentionally changing deps.
Should I commit package-lock.json for applications?
Yes. Applications and deployable services always commit the lockfile. Libraries published to npm may omit it, but internal packages should still lock for reproducible CI.
How do I fix lockfile out of sync errors?
rm -rf node_modules
npm install
git add package-lock.jsonRun tests before pushing the regenerated lockfile.
Does npm ci work without package-lock.json?
No. Generate one with npm install first, then switch CI to npm ci.
How do I audit the locked graph?
npm audit --audit-level=highAudit reads the lockfile graph, not just package.json ranges.
Should Docker COPY package-lock.json?
Yes. Copy lockfile before npm ci so dependency layers cache independently from app source changes.
What about optional dependencies?
Lockfiles pin optional packages too. npm ci installs them unless omitted with flags; document platform-specific optional deps in README.
Can I use npm ci with workspaces?
Yes. npm ci at the monorepo root installs all workspaces from the root lockfile.
How often should we refresh the lockfile?
Weekly or via automation (Renovate). Security patches should trigger a PR with CI green before merge.
Does --omit=dev affect lockfile integrity?
--omit=dev skips installing devDependencies but still requires a lockfile generated with them present at build time.
Related
- package.json Scripts -
npm cithennpm testin CI - Supply Chain: npm audit & Socket - audit the locked graph
- Package Managers Best Practices - one lockfile policy
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.