Lockfiles & Reproducible Installs
Lockfiles pin the exact dependency graph so laptops, CI, and production builds install identical packages every time.
Search across all documentation pages
Lockfiles pin the exact dependency graph so laptops, CI, and production builds install identical packages every time.
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:
node_modules.# 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 ci deletes node_modules and installs exactly from package-lock.json.deps stage uses --omit=dev for a smaller production image.npm ci so devDependencies (TypeScript) are available to compile.package-lock.json (npm) records resolved versions, integrity hashes, and nested structure.npm install may update the lockfile when ranges allow newer compatible versions.npm ci fails if package.json and lockfile disagree, protecting CI from drift.pnpm-lock.yaml; Yarn uses yarn.lock - only one per repo.| 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 |
{
"scripts": {
"verify:lockfile": "npm ci && npm run typecheck && npm test"
}
}verify:lockfile after merging dependency PRs to catch missing native rebuilds.package.json edits.npm ci exclusively in pipelines.package.json without the lockfile breaks teammates and CI. Fix: always commit both files together.package-lock.json plus yarn.lock causes unpredictable installs. Fix: delete unused lockfiles; document the chosen manager in README.npm install after resolving package.json.| 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 |
npm install may update the lockfile and reuse existing node_modules.npm ci requires a lockfile, removes node_modules, and installs exactly pinned versions.npm ci; developers use npm install when intentionally changing deps.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.
rm -rf node_modules
npm install
git add package-lock.jsonRun tests before pushing the regenerated lockfile.
No. Generate one with npm install first, then switch CI to npm ci.
npm audit --audit-level=highAudit reads the lockfile graph, not just package.json ranges.
Yes. Copy lockfile before npm ci so dependency layers cache independently from app source changes.
Lockfiles pin optional packages too. npm ci installs them unless omitted with flags; document platform-specific optional deps in README.
Yes. npm ci at the monorepo root installs all workspaces from the root lockfile.
Weekly or via automation (Renovate). Security patches should trigger a PR with CI green before merge.
--omit=dev skips installing devDependencies but still requires a lockfile generated with them present at build time.
npm ci then npm test in CIStack 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.
Reviewed by Chris St. John·Last updated Jul 18, 2026